mirror of
https://github.com/moghtech/komodo.git
synced 2026-11-05 18:56:42 -06:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51b16434a5 | ||
|
|
f49d73dba3 | ||
|
|
62723fa32f | ||
|
|
d1d17626b2 | ||
|
|
312f51e50b | ||
|
|
d68ea5cf45 | ||
|
|
71fc0bba97 | ||
|
|
1a7a0299de | ||
|
|
cd59da100f | ||
|
|
5a8ed8b81d | ||
|
|
4c9479a8bc | ||
|
|
19aa5fb260 | ||
|
|
75c0c967ac | ||
|
|
8832a05ffe | ||
|
|
7598978cd1 | ||
|
|
b868ad5794 | ||
|
|
be6a0db511 | ||
|
|
c360289984 | ||
|
|
23abdb85b0 | ||
|
|
b510d6dc41 | ||
|
|
f29c60a6e9 | ||
|
|
ee33fc98d9 | ||
|
|
729ffc9c3c | ||
|
|
395930094d | ||
|
|
79a16c6f22 | ||
|
|
4e4aa8c567 | ||
|
|
bfbcd33c1a | ||
|
|
01e2e0be11 | ||
|
|
2e6e409b85 | ||
|
|
30e87b6aff | ||
|
|
8423a53106 | ||
|
|
9e6c122313 | ||
|
|
d03d0b3b78 | ||
|
|
1bf76f1b57 | ||
|
|
7815639aeb | ||
|
|
af9fbf9667 |
@@ -10,7 +10,7 @@
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20.12.2"
|
||||
"version": "18.18.0"
|
||||
},
|
||||
"ghcr.io/devcontainers-community/features/deno:1": {
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository
|
||||
open_collective: komodo
|
||||
Generated
+362
-402
File diff suppressed because it is too large
Load Diff
+23
-23
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.17.0-dev-4"
|
||||
version = "1.17.0-dev-1"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -40,11 +40,11 @@ derive_variants = "1.0.0"
|
||||
mongo_indexed = "2.0.1"
|
||||
resolver_api = "3.0.0"
|
||||
toml_pretty = "1.1.2"
|
||||
mungos = "3.2.0"
|
||||
mungos = "1.1.0"
|
||||
svi = "1.0.1"
|
||||
|
||||
# ASYNC
|
||||
reqwest = { version = "0.12.12", default-features = false, features = ["json", "rustls-tls-native-roots"] }
|
||||
reqwest = { version = "0.12.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
tokio = { version = "1.43.0", features = ["full"] }
|
||||
tokio-util = "0.7.13"
|
||||
futures = "0.3.31"
|
||||
@@ -55,45 +55,45 @@ axum-extra = { version = "0.10.0", features = ["typed-header"] }
|
||||
tower-http = { version = "0.6.2", features = ["fs", "cors"] }
|
||||
axum-server = { version = "0.7.1", features = ["tls-rustls"] }
|
||||
axum = { version = "0.8.1", features = ["ws", "json", "macros"] }
|
||||
tokio-tungstenite = "0.26.2"
|
||||
tokio-tungstenite = "0.26.1"
|
||||
|
||||
# SER/DE
|
||||
ordered_hash_map = { version = "0.4.0", features = ["serde"] }
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
strum = { version = "0.27.1", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
serde_json = "1.0.135"
|
||||
serde_yaml = "0.9.34"
|
||||
toml = "0.8.20"
|
||||
toml = "0.8.19"
|
||||
|
||||
# ERROR
|
||||
anyhow = "1.0.97"
|
||||
thiserror = "2.0.12"
|
||||
anyhow = "1.0.95"
|
||||
thiserror = "2.0.11"
|
||||
|
||||
# LOGGING
|
||||
opentelemetry-otlp = { version = "0.28.0", features = ["tls-roots", "reqwest-rustls"] }
|
||||
opentelemetry_sdk = { version = "0.28.0", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = { version = "0.27.0", features = ["tls-roots", "reqwest-rustls"] }
|
||||
opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio"] }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["json"] }
|
||||
opentelemetry-semantic-conventions = "0.28.0"
|
||||
tracing-opentelemetry = "0.29.0"
|
||||
opentelemetry = "0.28.0"
|
||||
opentelemetry-semantic-conventions = "0.27.0"
|
||||
tracing-opentelemetry = "0.28.0"
|
||||
opentelemetry = "0.27.1"
|
||||
tracing = "0.1.41"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.5.31", features = ["derive"] }
|
||||
clap = { version = "4.5.26", features = ["derive"] }
|
||||
dotenvy = "0.15.7"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO / AUTH
|
||||
uuid = { version = "1.15.1", features = ["v4", "fast-rng", "serde"] }
|
||||
openidconnect = "4.0.0"
|
||||
uuid = { version = "1.12.0", features = ["v4", "fast-rng", "serde"] }
|
||||
openidconnect = "3.5.0"
|
||||
urlencoding = "2.1.3"
|
||||
nom_pem = "4.0.0"
|
||||
bcrypt = "0.17.0"
|
||||
bcrypt = "0.16.0"
|
||||
base64 = "0.22.1"
|
||||
rustls = "0.23.23"
|
||||
rustls = "0.23.21"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.8"
|
||||
rand = "0.9.0"
|
||||
rand = "0.8.5"
|
||||
jwt = "0.16.0"
|
||||
hex = "0.4.3"
|
||||
|
||||
@@ -102,8 +102,8 @@ bollard = "0.18.1"
|
||||
sysinfo = "0.33.1"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.5.17"
|
||||
aws-sdk-ec2 = "1.114.0"
|
||||
aws-config = "1.5.13"
|
||||
aws-sdk-ec2 = "1.101.0"
|
||||
|
||||
# MISC
|
||||
derive_builder = "0.20.2"
|
||||
|
||||
@@ -67,7 +67,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
) -> serror::Result<Update> {
|
||||
let mut action = resource::get_check_permissions::<Action>(
|
||||
&self.action,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
@@ -111,9 +111,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
let path = core_config().action_directory.join(&file);
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize Action file parent directory {parent:?}"))?;
|
||||
let _ = fs::create_dir_all(parent).await;
|
||||
}
|
||||
|
||||
fs::write(&path, contents).await.with_context(|| {
|
||||
@@ -125,6 +123,7 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
"Execute Action",
|
||||
None,
|
||||
format!("deno run --allow-all {}", path.display()),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
) -> serror::Result<Update> {
|
||||
let mut build = resource::get_check_permissions::<Build>(
|
||||
&self.build,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
@@ -517,7 +517,7 @@ impl Resolve<ExecuteArgs> for CancelBuild {
|
||||
) -> serror::Result<Update> {
|
||||
let build = resource::get_check_permissions::<Build>(
|
||||
&self.build,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -561,7 +561,7 @@ impl Resolve<ExecuteArgs> for UnpauseDeployment {
|
||||
ExecuteArgs { user, update }: &ExecuteArgs,
|
||||
) -> serror::Result<Update> {
|
||||
let (deployment, server) =
|
||||
setup_deployment_execution(&self.deployment, user).await?;
|
||||
setup_deployment_execution(&self.deployment, &user).await?;
|
||||
|
||||
// get the action state for the deployment (or insert default).
|
||||
let action_state = action_states()
|
||||
@@ -610,7 +610,7 @@ impl Resolve<ExecuteArgs> for StopDeployment {
|
||||
ExecuteArgs { user, update }: &ExecuteArgs,
|
||||
) -> serror::Result<Update> {
|
||||
let (deployment, server) =
|
||||
setup_deployment_execution(&self.deployment, user).await?;
|
||||
setup_deployment_execution(&self.deployment, &user).await?;
|
||||
|
||||
// get the action state for the deployment (or insert default).
|
||||
let action_state = action_states()
|
||||
|
||||
@@ -173,7 +173,7 @@ impl Resolve<ExecuteArgs> for BatchPullRepo {
|
||||
ExecuteArgs { user, .. }: &ExecuteArgs,
|
||||
) -> serror::Result<BatchExecutionResponse> {
|
||||
Ok(
|
||||
super::batch_execute::<BatchPullRepo>(&self.pattern, user)
|
||||
super::batch_execute::<BatchPullRepo>(&self.pattern, &user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -187,7 +187,7 @@ impl Resolve<ExecuteArgs> for PullRepo {
|
||||
) -> serror::Result<Update> {
|
||||
let mut repo = resource::get_check_permissions::<Repo>(
|
||||
&self.repo,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
@@ -438,7 +438,8 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
return handle_builder_early_return(
|
||||
update, repo.id, repo.name, false,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.map_err(Into::into);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ impl super::BatchExecute for BatchDeployStack {
|
||||
fn single_request(stack: String) -> ExecuteRequest {
|
||||
ExecuteRequest::DeployStack(DeployStack {
|
||||
stack,
|
||||
services: Vec::new(),
|
||||
service: None,
|
||||
stop_time: None,
|
||||
})
|
||||
}
|
||||
@@ -87,13 +87,10 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
if !self.services.is_empty() {
|
||||
if let Some(service) = &self.service {
|
||||
update.logs.push(Log::simple(
|
||||
&format!("Service/s",),
|
||||
format!(
|
||||
"Execution requested for Stack service/s {}",
|
||||
self.services.join(", ")
|
||||
),
|
||||
&format!("Service: {service}"),
|
||||
format!("Execution requested for Stack service {service}"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -180,13 +177,12 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
file_contents,
|
||||
missing_files,
|
||||
remote_errors,
|
||||
compose_config,
|
||||
commit_hash,
|
||||
commit_message,
|
||||
} = periphery_client(&server)?
|
||||
.request(ComposeUp {
|
||||
stack: stack.clone(),
|
||||
services: self.services,
|
||||
service: self.service,
|
||||
git_token,
|
||||
registry_token,
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
@@ -210,14 +206,12 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
let (
|
||||
deployed_services,
|
||||
deployed_contents,
|
||||
deployed_config,
|
||||
deployed_hash,
|
||||
deployed_message,
|
||||
) = if deployed {
|
||||
(
|
||||
Some(latest_services.clone()),
|
||||
Some(file_contents.clone()),
|
||||
compose_config,
|
||||
commit_hash.clone(),
|
||||
commit_message.clone(),
|
||||
)
|
||||
@@ -225,7 +219,6 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
(
|
||||
stack.info.deployed_services,
|
||||
stack.info.deployed_contents,
|
||||
stack.info.deployed_config,
|
||||
stack.info.deployed_hash,
|
||||
stack.info.deployed_message,
|
||||
)
|
||||
@@ -236,7 +229,6 @@ impl Resolve<ExecuteArgs> for DeployStack {
|
||||
deployed_project_name: project_name.into(),
|
||||
deployed_services,
|
||||
deployed_contents,
|
||||
deployed_config,
|
||||
deployed_hash,
|
||||
deployed_message,
|
||||
latest_services,
|
||||
@@ -374,7 +366,7 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
|
||||
|
||||
DeployStack {
|
||||
stack: stack.name,
|
||||
services: Vec::new(),
|
||||
service: None,
|
||||
stop_time: self.stop_time,
|
||||
}
|
||||
.resolve(&ExecuteArgs {
|
||||
@@ -387,18 +379,14 @@ impl Resolve<ExecuteArgs> for DeployStackIfChanged {
|
||||
|
||||
pub async fn pull_stack_inner(
|
||||
mut stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
server: &Server,
|
||||
mut update: Option<&mut Update>,
|
||||
update: Option<&mut Update>,
|
||||
) -> anyhow::Result<ComposePullResponse> {
|
||||
if let Some(update) = update.as_mut() {
|
||||
if !services.is_empty() {}
|
||||
if let (Some(service), Some(update)) = (&service, update) {
|
||||
update.logs.push(Log::simple(
|
||||
&format!("Service/s"),
|
||||
format!(
|
||||
"Execution requested for Stack service/s {}",
|
||||
services.join(", ")
|
||||
),
|
||||
&format!("Service: {service}"),
|
||||
format!("Execution requested for Stack service {service}"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -417,40 +405,10 @@ pub async fn pull_stack_inner(
|
||||
|| format!("Failed to get registry token in call to db. Stopping run. | {} | {}", stack.config.registry_provider, stack.config.registry_account),
|
||||
)?;
|
||||
|
||||
// interpolate variables / secrets
|
||||
if !stack.config.skip_secret_interp {
|
||||
let vars_and_secrets = get_variables_and_secrets().await?;
|
||||
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut stack.config.file_contents,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut stack.config.environment,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
if let Some(update) = update {
|
||||
add_interp_update_log(
|
||||
update,
|
||||
&global_replacers,
|
||||
&secret_replacers,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let res = periphery_client(server)?
|
||||
.request(ComposePull {
|
||||
stack,
|
||||
services,
|
||||
service,
|
||||
git_token,
|
||||
registry_token,
|
||||
})
|
||||
@@ -490,7 +448,7 @@ impl Resolve<ExecuteArgs> for PullStack {
|
||||
|
||||
let res = pull_stack_inner(
|
||||
stack,
|
||||
self.services,
|
||||
self.service,
|
||||
&server,
|
||||
Some(&mut update),
|
||||
)
|
||||
@@ -512,7 +470,7 @@ impl Resolve<ExecuteArgs> for StartStack {
|
||||
) -> serror::Result<Update> {
|
||||
execute_compose::<StartStack>(
|
||||
&self.stack,
|
||||
self.services,
|
||||
self.service,
|
||||
user,
|
||||
|state| state.starting = true,
|
||||
update.clone(),
|
||||
@@ -531,7 +489,7 @@ impl Resolve<ExecuteArgs> for RestartStack {
|
||||
) -> serror::Result<Update> {
|
||||
execute_compose::<RestartStack>(
|
||||
&self.stack,
|
||||
self.services,
|
||||
self.service,
|
||||
user,
|
||||
|state| {
|
||||
state.restarting = true;
|
||||
@@ -552,7 +510,7 @@ impl Resolve<ExecuteArgs> for PauseStack {
|
||||
) -> serror::Result<Update> {
|
||||
execute_compose::<PauseStack>(
|
||||
&self.stack,
|
||||
self.services,
|
||||
self.service,
|
||||
user,
|
||||
|state| state.pausing = true,
|
||||
update.clone(),
|
||||
@@ -571,7 +529,7 @@ impl Resolve<ExecuteArgs> for UnpauseStack {
|
||||
) -> serror::Result<Update> {
|
||||
execute_compose::<UnpauseStack>(
|
||||
&self.stack,
|
||||
self.services,
|
||||
self.service,
|
||||
user,
|
||||
|state| state.unpausing = true,
|
||||
update.clone(),
|
||||
@@ -590,7 +548,7 @@ impl Resolve<ExecuteArgs> for StopStack {
|
||||
) -> serror::Result<Update> {
|
||||
execute_compose::<StopStack>(
|
||||
&self.stack,
|
||||
self.services,
|
||||
self.service,
|
||||
user,
|
||||
|state| state.stopping = true,
|
||||
update.clone(),
|
||||
@@ -606,7 +564,7 @@ impl super::BatchExecute for BatchDestroyStack {
|
||||
fn single_request(stack: String) -> ExecuteRequest {
|
||||
ExecuteRequest::DestroyStack(DestroyStack {
|
||||
stack,
|
||||
services: Vec::new(),
|
||||
service: None,
|
||||
remove_orphans: false,
|
||||
stop_time: None,
|
||||
})
|
||||
@@ -633,7 +591,7 @@ impl Resolve<ExecuteArgs> for DestroyStack {
|
||||
) -> serror::Result<Update> {
|
||||
execute_compose::<DestroyStack>(
|
||||
&self.stack,
|
||||
self.services,
|
||||
self.service,
|
||||
user,
|
||||
|state| state.destroying = true,
|
||||
update.clone(),
|
||||
|
||||
@@ -25,13 +25,16 @@ use komodo_client::{
|
||||
},
|
||||
};
|
||||
use mongo_indexed::doc;
|
||||
use mungos::{by_id::update_one_by_id, mongodb::bson::oid::ObjectId};
|
||||
use mungos::{
|
||||
by_id::update_one_by_id,
|
||||
mongodb::bson::{oid::ObjectId, to_document},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::write::WriteArgs,
|
||||
helpers::{query::get_id_to_tags, update::update_update},
|
||||
resource,
|
||||
resource::{self, refresh_resource_sync_state_cache},
|
||||
state::{action_states, db_client},
|
||||
sync::{
|
||||
deploy::{
|
||||
@@ -58,7 +61,7 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
} = self;
|
||||
let sync = resource::get_check_permissions::<
|
||||
entities::sync::ResourceSync,
|
||||
>(&sync, user, PermissionLevel::Execute)
|
||||
>(&sync, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
// get the action state for the sync (or insert default).
|
||||
@@ -606,6 +609,21 @@ impl Resolve<ExecuteArgs> for RunSync {
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
// but will fail to update cache in that case.
|
||||
if let Ok(update_doc) = to_document(&update) {
|
||||
let _ = update_one_by_id(
|
||||
&db.updates,
|
||||
&update.id,
|
||||
mungos::update::Update::Set(update_doc),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
refresh_resource_sync_state_cache().await;
|
||||
}
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
|
||||
@@ -45,7 +45,7 @@ impl Resolve<ReadArgs> for ListActions {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Action>(self.query, user, &all_tags)
|
||||
resource::list_for_user::<Action>(self.query, &user, &all_tags)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ impl Resolve<ReadArgs> for ListFullActions {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Action>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -77,7 +77,7 @@ impl Resolve<ReadArgs> for GetActionActionState {
|
||||
) -> serror::Result<ActionActionState> {
|
||||
let action = resource::get_check_permissions::<Action>(
|
||||
&self.action,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -98,7 +98,7 @@ impl Resolve<ReadArgs> for GetActionsSummary {
|
||||
) -> serror::Result<GetActionsSummaryResponse> {
|
||||
let actions = resource::list_full_for_user::<Action>(
|
||||
Default::default(),
|
||||
user,
|
||||
&user,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Resolve<ReadArgs> for ListFullAlerters {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Alerter>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -75,7 +75,7 @@ impl Resolve<ReadArgs> for GetAlertersSummary {
|
||||
) -> serror::Result<GetAlertersSummaryResponse> {
|
||||
let query = match resource::get_resource_object_ids_for_user::<
|
||||
Alerter,
|
||||
>(user)
|
||||
>(&user)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
|
||||
@@ -75,7 +75,7 @@ impl Resolve<ReadArgs> for GetBuildersSummary {
|
||||
) -> serror::Result<GetBuildersSummaryResponse> {
|
||||
let query = match resource::get_resource_object_ids_for_user::<
|
||||
Builder,
|
||||
>(user)
|
||||
>(&user)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
|
||||
@@ -281,7 +281,7 @@ impl Resolve<ReadArgs> for ListCommonDeploymentExtraArgs {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let deployments = resource::list_full_for_user::<Deployment>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resources matching query")?;
|
||||
|
||||
@@ -167,8 +167,8 @@ enum ReadRequest {
|
||||
GetStack(GetStack),
|
||||
GetStackActionState(GetStackActionState),
|
||||
GetStackWebhooksEnabled(GetStackWebhooksEnabled),
|
||||
GetStackLog(GetStackLog),
|
||||
SearchStackLog(SearchStackLog),
|
||||
GetStackServiceLog(GetStackServiceLog),
|
||||
SearchStackServiceLog(SearchStackServiceLog),
|
||||
ListStacks(ListStacks),
|
||||
ListFullStacks(ListFullStacks),
|
||||
ListStackServices(ListStackServices),
|
||||
@@ -381,17 +381,17 @@ impl Resolve<ReadArgs> for ListGitProvidersFromConfig {
|
||||
let (builds, repos, syncs) = tokio::try_join!(
|
||||
resource::list_full_for_user::<Build>(
|
||||
Default::default(),
|
||||
user,
|
||||
&user,
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<Repo>(
|
||||
Default::default(),
|
||||
user,
|
||||
&user,
|
||||
&[]
|
||||
),
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
Default::default(),
|
||||
user,
|
||||
&user,
|
||||
&[]
|
||||
),
|
||||
)?;
|
||||
|
||||
@@ -63,7 +63,7 @@ impl Resolve<ReadArgs> for ListFullProcedures {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Procedure>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ impl Resolve<ReadArgs> for ListRepos {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Repo>(self.query, user, &all_tags)
|
||||
resource::list_for_user::<Repo>(self.query, &user, &all_tags)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ impl Resolve<ReadArgs> for ListFullRepos {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Repo>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ impl Resolve<ReadArgs> for ListServers {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<Server>(self.query, user, &all_tags)
|
||||
resource::list_for_user::<Server>(self.query, &user, &all_tags)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ impl Resolve<ReadArgs> for ListFullServers {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<Server>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -387,7 +387,7 @@ impl Resolve<ReadArgs> for ListAllDockerContainers {
|
||||
) -> serror::Result<ListAllDockerContainersResponse> {
|
||||
let servers = resource::list_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
&user,
|
||||
&[],
|
||||
)
|
||||
.await?
|
||||
@@ -533,7 +533,7 @@ impl Resolve<ReadArgs> for GetResourceMatchingContainer {
|
||||
let stacks =
|
||||
resource::list_full_for_user_using_document::<Stack>(
|
||||
doc! { "config.server_id": &server.id },
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ impl Resolve<ReadArgs> for GetServerTemplatesSummary {
|
||||
) -> serror::Result<GetServerTemplatesSummaryResponse> {
|
||||
let query = match resource::get_resource_object_ids_for_user::<
|
||||
ServerTemplate,
|
||||
>(user)
|
||||
>(&user)
|
||||
.await?
|
||||
{
|
||||
Some(ids) => doc! {
|
||||
|
||||
@@ -10,7 +10,7 @@ use komodo_client::{
|
||||
},
|
||||
};
|
||||
use periphery_client::api::compose::{
|
||||
GetComposeLog, GetComposeLogSearch,
|
||||
GetComposeServiceLog, GetComposeServiceLogSearch,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
@@ -64,14 +64,14 @@ impl Resolve<ReadArgs> for ListStackServices {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetStackLog {
|
||||
impl Resolve<ReadArgs> for GetStackServiceLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<GetStackLogResponse> {
|
||||
let GetStackLog {
|
||||
) -> serror::Result<GetStackServiceLogResponse> {
|
||||
let GetStackServiceLog {
|
||||
stack,
|
||||
services,
|
||||
service,
|
||||
tail,
|
||||
timestamps,
|
||||
} = self;
|
||||
@@ -79,26 +79,26 @@ impl Resolve<ReadArgs> for GetStackLog {
|
||||
get_stack_and_server(&stack, user, PermissionLevel::Read, true)
|
||||
.await?;
|
||||
let res = periphery_client(&server)?
|
||||
.request(GetComposeLog {
|
||||
.request(GetComposeServiceLog {
|
||||
project: stack.project_name(false),
|
||||
services,
|
||||
service,
|
||||
tail,
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("Failed to get stack log from periphery")?;
|
||||
.context("failed to get stack service log from periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for SearchStackLog {
|
||||
impl Resolve<ReadArgs> for SearchStackServiceLog {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<SearchStackLogResponse> {
|
||||
let SearchStackLog {
|
||||
) -> serror::Result<SearchStackServiceLogResponse> {
|
||||
let SearchStackServiceLog {
|
||||
stack,
|
||||
services,
|
||||
service,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
@@ -108,16 +108,16 @@ impl Resolve<ReadArgs> for SearchStackLog {
|
||||
get_stack_and_server(&stack, user, PermissionLevel::Read, true)
|
||||
.await?;
|
||||
let res = periphery_client(&server)?
|
||||
.request(GetComposeLogSearch {
|
||||
.request(GetComposeServiceLogSearch {
|
||||
project: stack.project_name(false),
|
||||
services,
|
||||
service,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
timestamps,
|
||||
})
|
||||
.await
|
||||
.context("Failed to search stack log from periphery")?;
|
||||
.context("failed to get stack service log from periphery")?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ impl Resolve<ReadArgs> for ListCommonStackExtraArgs {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resources matching query")?;
|
||||
@@ -164,7 +164,7 @@ impl Resolve<ReadArgs> for ListCommonStackBuildExtraArgs {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let stacks = resource::list_full_for_user::<Stack>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await
|
||||
.context("failed to get resources matching query")?;
|
||||
|
||||
@@ -6,6 +6,7 @@ use komodo_client::{
|
||||
permission::PermissionLevel,
|
||||
sync::{
|
||||
ResourceSync, ResourceSyncActionState, ResourceSyncListItem,
|
||||
ResourceSyncState,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -15,7 +16,7 @@ use crate::{
|
||||
config::core_config,
|
||||
helpers::query::get_all_tags,
|
||||
resource,
|
||||
state::{action_states, github_client},
|
||||
state::{action_states, github_client, resource_sync_state_cache},
|
||||
};
|
||||
|
||||
use super::ReadArgs;
|
||||
@@ -28,7 +29,7 @@ impl Resolve<ReadArgs> for GetResourceSync {
|
||||
Ok(
|
||||
resource::get_check_permissions::<ResourceSync>(
|
||||
&self.sync,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?,
|
||||
@@ -48,7 +49,7 @@ impl Resolve<ReadArgs> for ListResourceSyncs {
|
||||
};
|
||||
Ok(
|
||||
resource::list_for_user::<ResourceSync>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -67,7 +68,7 @@ impl Resolve<ReadArgs> for ListFullResourceSyncs {
|
||||
};
|
||||
Ok(
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
self.query, user, &all_tags,
|
||||
self.query, &user, &all_tags,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -111,6 +112,7 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
|
||||
|
||||
let mut res = GetResourceSyncsSummaryResponse::default();
|
||||
|
||||
let cache = resource_sync_state_cache();
|
||||
let action_states = action_states();
|
||||
|
||||
for resource_sync in resource_syncs {
|
||||
@@ -129,15 +131,29 @@ impl Resolve<ReadArgs> for GetResourceSyncsSummary {
|
||||
res.failed += 1;
|
||||
continue;
|
||||
}
|
||||
if action_states
|
||||
.resource_sync
|
||||
.get(&resource_sync.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.get()?
|
||||
.syncing
|
||||
{
|
||||
res.syncing += 1;
|
||||
|
||||
match (
|
||||
cache.get(&resource_sync.id).await.unwrap_or_default(),
|
||||
action_states
|
||||
.resource_sync
|
||||
.get(&resource_sync.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.get()?,
|
||||
) {
|
||||
(_, action_states) if action_states.syncing => {
|
||||
res.syncing += 1;
|
||||
}
|
||||
(ResourceSyncState::Ok, _) => res.ok += 1,
|
||||
(ResourceSyncState::Failed, _) => res.failed += 1,
|
||||
(ResourceSyncState::Unknown, _) => res.unknown += 1,
|
||||
// will never come off the cache in the building state, since that comes from action states
|
||||
(ResourceSyncState::Syncing, _) => {
|
||||
unreachable!()
|
||||
}
|
||||
(ResourceSyncState::Pending, _) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Alerter>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -59,7 +59,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Builder>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -69,7 +69,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Server>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -79,7 +79,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Stack>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -89,7 +89,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Deployment>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -99,7 +99,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Build>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -109,7 +109,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Repo>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -119,7 +119,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Procedure>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -129,7 +129,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<Action>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -139,7 +139,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_for_user::<ServerTemplate>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -149,7 +149,7 @@ impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
||||
targets.extend(
|
||||
resource::list_full_for_user::<ResourceSync>(
|
||||
ResourceQuery::builder().tags(self.tags.clone()).build(),
|
||||
user,
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
@@ -198,7 +198,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::Alerter(id) => {
|
||||
let alerter = resource::get_check_permissions::<Alerter>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -212,7 +212,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -231,7 +231,9 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
let template = resource::get_check_permissions::<
|
||||
ServerTemplate,
|
||||
>(&id, user, PermissionLevel::Read)
|
||||
>(
|
||||
&id, &user, PermissionLevel::Read
|
||||
)
|
||||
.await?;
|
||||
res.server_templates.push(
|
||||
convert_resource::<ServerTemplate>(
|
||||
@@ -245,7 +247,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::Server(id) => {
|
||||
let server = resource::get_check_permissions::<Server>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -260,7 +262,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
let mut builder =
|
||||
resource::get_check_permissions::<Builder>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -275,7 +277,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::Build(id) => {
|
||||
let mut build = resource::get_check_permissions::<Build>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -291,7 +293,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
let mut deployment = resource::get_check_permissions::<
|
||||
Deployment,
|
||||
>(
|
||||
&id, user, PermissionLevel::Read
|
||||
&id, &user, PermissionLevel::Read
|
||||
)
|
||||
.await?;
|
||||
Deployment::replace_ids(&mut deployment, &all);
|
||||
@@ -305,7 +307,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::Repo(id) => {
|
||||
let mut repo = resource::get_check_permissions::<Repo>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -320,7 +322,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::Stack(id) => {
|
||||
let mut stack = resource::get_check_permissions::<Stack>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -336,7 +338,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
let mut procedure = resource::get_check_permissions::<
|
||||
Procedure,
|
||||
>(
|
||||
&id, user, PermissionLevel::Read
|
||||
&id, &user, PermissionLevel::Read
|
||||
)
|
||||
.await?;
|
||||
Procedure::replace_ids(&mut procedure, &all);
|
||||
@@ -350,7 +352,7 @@ impl Resolve<ReadArgs> for ExportResourcesToToml {
|
||||
ResourceTarget::Action(id) => {
|
||||
let mut action = resource::get_check_permissions::<Action>(
|
||||
&id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -43,7 +43,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
self.query
|
||||
} else {
|
||||
let server_query =
|
||||
resource::get_resource_ids_for_user::<Server>(user)
|
||||
resource::get_resource_ids_for_user::<Server>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -53,7 +53,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Server" });
|
||||
|
||||
let deployment_query =
|
||||
resource::get_resource_ids_for_user::<Deployment>(user)
|
||||
resource::get_resource_ids_for_user::<Deployment>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -63,7 +63,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Deployment" });
|
||||
|
||||
let stack_query =
|
||||
resource::get_resource_ids_for_user::<Stack>(user)
|
||||
resource::get_resource_ids_for_user::<Stack>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -73,7 +73,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Stack" });
|
||||
|
||||
let build_query =
|
||||
resource::get_resource_ids_for_user::<Build>(user)
|
||||
resource::get_resource_ids_for_user::<Build>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -83,7 +83,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Build" });
|
||||
|
||||
let repo_query =
|
||||
resource::get_resource_ids_for_user::<Repo>(user)
|
||||
resource::get_resource_ids_for_user::<Repo>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -93,7 +93,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Repo" });
|
||||
|
||||
let procedure_query =
|
||||
resource::get_resource_ids_for_user::<Procedure>(user)
|
||||
resource::get_resource_ids_for_user::<Procedure>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -103,7 +103,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
|
||||
|
||||
let action_query =
|
||||
resource::get_resource_ids_for_user::<Action>(user)
|
||||
resource::get_resource_ids_for_user::<Action>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -113,7 +113,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Action" });
|
||||
|
||||
let builder_query =
|
||||
resource::get_resource_ids_for_user::<Builder>(user)
|
||||
resource::get_resource_ids_for_user::<Builder>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -123,7 +123,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Builder" });
|
||||
|
||||
let alerter_query =
|
||||
resource::get_resource_ids_for_user::<Alerter>(user)
|
||||
resource::get_resource_ids_for_user::<Alerter>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -133,7 +133,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
.unwrap_or_else(|| doc! { "target.type": "Alerter" });
|
||||
|
||||
let server_template_query =
|
||||
resource::get_resource_ids_for_user::<ServerTemplate>(user)
|
||||
resource::get_resource_ids_for_user::<ServerTemplate>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
@@ -144,7 +144,7 @@ impl Resolve<ReadArgs> for ListUpdates {
|
||||
|
||||
let resource_sync_query =
|
||||
resource::get_resource_ids_for_user::<ResourceSync>(
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
@@ -247,7 +247,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Server(id) => {
|
||||
resource::get_check_permissions::<Server>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -255,7 +255,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Deployment(id) => {
|
||||
resource::get_check_permissions::<Deployment>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -263,7 +263,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Build(id) => {
|
||||
resource::get_check_permissions::<Build>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -271,7 +271,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Repo(id) => {
|
||||
resource::get_check_permissions::<Repo>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -279,7 +279,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Builder(id) => {
|
||||
resource::get_check_permissions::<Builder>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -287,7 +287,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Alerter(id) => {
|
||||
resource::get_check_permissions::<Alerter>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -295,7 +295,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Procedure(id) => {
|
||||
resource::get_check_permissions::<Procedure>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -303,7 +303,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Action(id) => {
|
||||
resource::get_check_permissions::<Action>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -311,7 +311,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
resource::get_check_permissions::<ServerTemplate>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -319,7 +319,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::ResourceSync(id) => {
|
||||
resource::get_check_permissions::<ResourceSync>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
@@ -327,7 +327,7 @@ impl Resolve<ReadArgs> for GetUpdate {
|
||||
ResourceTarget::Stack(id) => {
|
||||
resource::get_check_permissions::<Stack>(
|
||||
id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -32,12 +32,12 @@ impl Resolve<WriteArgs> for CopyAction {
|
||||
let Action { config, .. } =
|
||||
resource::get_check_permissions::<Action>(
|
||||
&self.id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
Ok(
|
||||
resource::create::<Action>(&self.name, config.into(), user)
|
||||
resource::create::<Action>(&self.name, config.into(), &user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Resolve<WriteArgs> for CopyAlerter {
|
||||
let Alerter { config, .. } =
|
||||
resource::get_check_permissions::<Alerter>(
|
||||
&self.id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -308,7 +308,7 @@ impl Resolve<WriteArgs> for DeleteBuildWebhook {
|
||||
|
||||
let build = resource::get_check_permissions::<Build>(
|
||||
&self.build,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -37,7 +37,7 @@ impl Resolve<WriteArgs> for CopyBuilder {
|
||||
)
|
||||
.await?;
|
||||
Ok(
|
||||
resource::create::<Builder>(&self.name, config.into(), user)
|
||||
resource::create::<Builder>(&self.name, config.into(), &user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -73,7 +73,7 @@ impl Resolve<WriteArgs> for RenameBuilder {
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Update> {
|
||||
Ok(
|
||||
resource::rename::<Builder>(&self.id, &self.name, user)
|
||||
resource::rename::<Builder>(&self.id, &self.name, &user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Resolve<WriteArgs> for CopyDeployment {
|
||||
resource::create::<Deployment>(
|
||||
&self.name,
|
||||
config.into(),
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -157,7 +157,7 @@ impl Resolve<WriteArgs> for CreateDeploymentFromContainer {
|
||||
}
|
||||
|
||||
Ok(
|
||||
resource::create::<Deployment>(&self.name, config, user)
|
||||
resource::create::<Deployment>(&self.name, config, &user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -180,7 +180,7 @@ impl Resolve<WriteArgs> for UpdateDeployment {
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Deployment> {
|
||||
Ok(
|
||||
resource::update::<Deployment>(&self.id, self.config, user)
|
||||
resource::update::<Deployment>(&self.id, self.config, &user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -224,7 +224,7 @@ impl Resolve<WriteArgs> for RenameDeployment {
|
||||
}
|
||||
|
||||
let mut update =
|
||||
make_update(&deployment, Operation::RenameDeployment, user);
|
||||
make_update(&deployment, Operation::RenameDeployment, &user);
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().deployments,
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Server>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Deployment>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Build>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -57,7 +57,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Repo>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Builder>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Alerter>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Procedure>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Action>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -97,7 +97,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<ServerTemplate>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<ResourceSync>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ impl Resolve<WriteArgs> for UpdateDescription {
|
||||
resource::update_description::<Stack>(
|
||||
&id,
|
||||
&self.description,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Resolve<WriteArgs> for CopyProcedure {
|
||||
let Procedure { config, .. } =
|
||||
resource::get_check_permissions::<Procedure>(
|
||||
&self.id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Resolve<WriteArgs> for CreateGitProviderAccount {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::CreateGitProviderAccount,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
account.id = db_client()
|
||||
@@ -114,7 +114,7 @@ impl Resolve<WriteArgs> for UpdateGitProviderAccount {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::UpdateGitProviderAccount,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
let account = to_document(&self.account).context(
|
||||
@@ -173,7 +173,7 @@ impl Resolve<WriteArgs> for DeleteGitProviderAccount {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::UpdateGitProviderAccount,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
let db = db_client();
|
||||
@@ -235,7 +235,7 @@ impl Resolve<WriteArgs> for CreateDockerRegistryAccount {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::CreateDockerRegistryAccount,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
account.id = db_client()
|
||||
@@ -298,8 +298,8 @@ impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
|
||||
if username.is_empty() {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"cannot update docker registry account with empty username"
|
||||
)
|
||||
"cannot update docker registry account with empty username"
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
@@ -310,7 +310,7 @@ impl Resolve<WriteArgs> for UpdateDockerRegistryAccount {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::UpdateDockerRegistryAccount,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
let account = to_document(&self.account).context(
|
||||
@@ -373,7 +373,7 @@ impl Resolve<WriteArgs> for DeleteDockerRegistryAccount {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::UpdateDockerRegistryAccount,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
let db = db_client();
|
||||
|
||||
@@ -53,12 +53,12 @@ impl Resolve<WriteArgs> for CopyRepo {
|
||||
let Repo { config, .. } =
|
||||
resource::get_check_permissions::<Repo>(
|
||||
&self.id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
Ok(
|
||||
resource::create::<Repo>(&self.name, config.into(), user)
|
||||
resource::create::<Repo>(&self.name, config.into(), &user)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl Resolve<WriteArgs> for RenameRepo {
|
||||
) -> serror::Result<Update> {
|
||||
let repo = resource::get_check_permissions::<Repo>(
|
||||
&self.id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
@@ -98,7 +98,7 @@ impl Resolve<WriteArgs> for RenameRepo {
|
||||
|| !repo.config.path.is_empty()
|
||||
{
|
||||
return Ok(
|
||||
resource::rename::<Repo>(&repo.id, &self.name, user).await?,
|
||||
resource::rename::<Repo>(&repo.id, &self.name, &user).await?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ impl Resolve<WriteArgs> for RenameRepo {
|
||||
|
||||
let name = to_komodo_name(&self.name);
|
||||
|
||||
let mut update = make_update(&repo, Operation::RenameRepo, user);
|
||||
let mut update = make_update(&repo, Operation::RenameRepo, &user);
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().repos,
|
||||
@@ -171,7 +171,7 @@ impl Resolve<WriteArgs> for RefreshRepoCache {
|
||||
// repo should be able to do this.
|
||||
let repo = resource::get_check_permissions::<Repo>(
|
||||
&self.repo,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -77,7 +77,7 @@ impl Resolve<WriteArgs> for CreateNetwork {
|
||||
let periphery = periphery_client(&server)?;
|
||||
|
||||
let mut update =
|
||||
make_update(&server, Operation::CreateNetwork, user);
|
||||
make_update(&server, Operation::CreateNetwork, &user);
|
||||
update.status = UpdateStatus::InProgress;
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ impl Resolve<WriteArgs> for CreateServerTemplate {
|
||||
resource::create::<ServerTemplate>(
|
||||
&self.name,
|
||||
self.config,
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -40,7 +40,7 @@ impl Resolve<WriteArgs> for CopyServerTemplate {
|
||||
let ServerTemplate { config, .. } =
|
||||
resource::get_check_permissions::<ServerTemplate>(
|
||||
&self.id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
@@ -48,7 +48,7 @@ impl Resolve<WriteArgs> for CopyServerTemplate {
|
||||
resource::create::<ServerTemplate>(
|
||||
&self.name,
|
||||
config.into(),
|
||||
user,
|
||||
&user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
|
||||
@@ -102,7 +102,6 @@ impl Resolve<WriteArgs> for RenameStack {
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for WriteStackFileContents {
|
||||
#[instrument(name = "WriteStackFileContents", skip(user))]
|
||||
async fn resolve(
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
@@ -114,7 +113,7 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
|
||||
} = self;
|
||||
let (mut stack, server) = get_stack_and_server(
|
||||
&stack,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
true,
|
||||
)
|
||||
@@ -127,7 +126,7 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
|
||||
}
|
||||
|
||||
let mut update =
|
||||
make_update(&stack, Operation::WriteStackContents, user);
|
||||
make_update(&stack, Operation::WriteStackContents, &user);
|
||||
|
||||
update.push_simple_log("File contents to write", &contents);
|
||||
|
||||
@@ -149,7 +148,7 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
|
||||
}
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"Write File Contents",
|
||||
"Write file contents",
|
||||
format_serror(&e.into()),
|
||||
);
|
||||
}
|
||||
@@ -187,7 +186,7 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
|
||||
}
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"Write File Contents",
|
||||
"Write file contents",
|
||||
format_serror(&e.into()),
|
||||
);
|
||||
}
|
||||
@@ -372,7 +371,6 @@ impl Resolve<WriteArgs> for RefreshStackCache {
|
||||
deployed_services: stack.info.deployed_services.clone(),
|
||||
deployed_project_name: stack.info.deployed_project_name.clone(),
|
||||
deployed_contents: stack.info.deployed_contents.clone(),
|
||||
deployed_config: stack.info.deployed_config.clone(),
|
||||
deployed_hash: stack.info.deployed_hash.clone(),
|
||||
deployed_message: stack.info.deployed_message.clone(),
|
||||
latest_services,
|
||||
@@ -402,7 +400,7 @@ impl Resolve<WriteArgs> for RefreshStackCache {
|
||||
if state == ServerState::Ok {
|
||||
let name = stack.name.clone();
|
||||
if let Err(e) =
|
||||
pull_stack_inner(stack, Vec::new(), &server, None).await
|
||||
pull_stack_inner(stack, None, &server, None).await
|
||||
{
|
||||
warn!(
|
||||
"Failed to pull latest images for Stack {name} | {e:#}",
|
||||
|
||||
+123
-196
@@ -45,11 +45,10 @@ use crate::{
|
||||
api::read::ReadArgs,
|
||||
config::core_config,
|
||||
helpers::{
|
||||
git_token,
|
||||
query::get_id_to_tags,
|
||||
update::{add_update, make_update, update_update},
|
||||
},
|
||||
resource,
|
||||
resource::{self, refresh_resource_sync_state_cache},
|
||||
state::{db_client, github_client},
|
||||
sync::{
|
||||
deploy::SyncDeployParams, remote::RemoteResources,
|
||||
@@ -66,8 +65,12 @@ impl Resolve<WriteArgs> for CreateResourceSync {
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<ResourceSync> {
|
||||
Ok(
|
||||
resource::create::<ResourceSync>(&self.name, self.config, user)
|
||||
.await?,
|
||||
resource::create::<ResourceSync>(
|
||||
&self.name,
|
||||
self.config,
|
||||
&user,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -81,7 +84,7 @@ impl Resolve<WriteArgs> for CopyResourceSync {
|
||||
let ResourceSync { config, .. } =
|
||||
resource::get_check_permissions::<ResourceSync>(
|
||||
&self.id,
|
||||
user,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
@@ -132,184 +135,19 @@ impl Resolve<WriteArgs> for RenameResourceSync {
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_sync_file_contents_on_host(
|
||||
req: WriteSyncFileContents,
|
||||
args: &WriteArgs,
|
||||
sync: ResourceSync,
|
||||
mut update: Update,
|
||||
) -> serror::Result<Update> {
|
||||
let WriteSyncFileContents {
|
||||
sync: _,
|
||||
resource_path,
|
||||
file_path,
|
||||
contents,
|
||||
} = req;
|
||||
|
||||
let root = core_config()
|
||||
.sync_directory
|
||||
.join(to_komodo_name(&sync.name));
|
||||
let file_path =
|
||||
file_path.parse::<PathBuf>().context("Invalid file path")?;
|
||||
let resource_path = resource_path
|
||||
.parse::<PathBuf>()
|
||||
.context("Invalid resource path")?;
|
||||
let full_path = root.join(&resource_path).join(&file_path);
|
||||
|
||||
if let Some(parent) = full_path.parent() {
|
||||
fs::create_dir_all(parent).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to initialize resource file parent directory {parent:?}"
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
fs::write(&full_path, &contents).await.with_context(|| {
|
||||
format!("Failed to write resource file contents to {full_path:?}")
|
||||
})
|
||||
{
|
||||
update.push_error_log("Write File", format_serror(&e.into()));
|
||||
} else {
|
||||
update.push_simple_log(
|
||||
"Write File",
|
||||
format!("File written to {full_path:?}"),
|
||||
);
|
||||
};
|
||||
|
||||
if !all_logs_success(&update.logs) {
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
if let Err(e) = (RefreshResourceSyncPending { sync: sync.name })
|
||||
.resolve(args)
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh failed",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
async fn write_sync_file_contents_git(
|
||||
req: WriteSyncFileContents,
|
||||
args: &WriteArgs,
|
||||
sync: ResourceSync,
|
||||
mut update: Update,
|
||||
) -> serror::Result<Update> {
|
||||
let WriteSyncFileContents {
|
||||
sync: _,
|
||||
resource_path,
|
||||
file_path,
|
||||
contents,
|
||||
} = req;
|
||||
|
||||
let mut clone_args: CloneArgs = (&sync).into();
|
||||
let root = clone_args.unique_path(&core_config().repo_directory)?;
|
||||
|
||||
let file_path =
|
||||
file_path.parse::<PathBuf>().context("Invalid file path")?;
|
||||
let resource_path = resource_path
|
||||
.parse::<PathBuf>()
|
||||
.context("Invalid resource path")?;
|
||||
let full_path = root.join(&resource_path).join(&file_path);
|
||||
|
||||
if let Some(parent) = full_path.parent() {
|
||||
fs::create_dir_all(parent).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to initialize resource file parent directory {parent:?}"
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Ensure the folder is initialized as git repo.
|
||||
// This allows a new file to be committed on a branch that may not exist.
|
||||
if !root.join(".git").exists() {
|
||||
let access_token = if let Some(account) = &clone_args.account {
|
||||
git_token(&clone_args.provider, account, |https| clone_args.https = https)
|
||||
.await
|
||||
.with_context(
|
||||
|| format!("Failed to get git token in call to db. Stopping run. | {} | {account}", clone_args.provider),
|
||||
)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
git::init_folder_as_repo(
|
||||
&root,
|
||||
&clone_args,
|
||||
access_token.as_deref(),
|
||||
&mut update.logs,
|
||||
)
|
||||
.await;
|
||||
|
||||
if !all_logs_success(&update.logs) {
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
return Ok(update);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
fs::write(&full_path, &contents).await.with_context(|| {
|
||||
format!("Failed to write resource file contents to {full_path:?}")
|
||||
})
|
||||
{
|
||||
update.push_error_log("Write File", format_serror(&e.into()));
|
||||
} else {
|
||||
update.push_simple_log(
|
||||
"Write File",
|
||||
format!("File written to {full_path:?}"),
|
||||
);
|
||||
};
|
||||
|
||||
if !all_logs_success(&update.logs) {
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
let commit_res = git::commit_file(
|
||||
&format!("{}: Commit Resource File", args.user.username),
|
||||
&root,
|
||||
&resource_path.join(&file_path),
|
||||
&sync.config.branch,
|
||||
)
|
||||
.await;
|
||||
|
||||
update.logs.extend(commit_res.logs);
|
||||
|
||||
if let Err(e) = (RefreshResourceSyncPending { sync: sync.name })
|
||||
.resolve(args)
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh failed",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for WriteSyncFileContents {
|
||||
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
|
||||
let WriteSyncFileContents {
|
||||
sync,
|
||||
resource_path,
|
||||
file_path,
|
||||
contents,
|
||||
} = self;
|
||||
let WriteArgs { user } = args;
|
||||
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&self.sync,
|
||||
&args.user,
|
||||
&sync,
|
||||
user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
@@ -317,22 +155,96 @@ impl Resolve<WriteArgs> for WriteSyncFileContents {
|
||||
if !sync.config.files_on_host && sync.config.repo.is_empty() {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"This method is only for 'files on host' or 'repo' based syncs."
|
||||
)
|
||||
"This method is only for files on host, or repo based syncs."
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut update =
|
||||
make_update(&sync, Operation::WriteSyncContents, &args.user);
|
||||
make_update(&sync, Operation::WriteSyncContents, &user);
|
||||
|
||||
update.push_simple_log("File contents", &self.contents);
|
||||
update.push_simple_log("File contents", &contents);
|
||||
|
||||
let root = if sync.config.files_on_host {
|
||||
core_config()
|
||||
.sync_directory
|
||||
.join(to_komodo_name(&sync.name))
|
||||
} else {
|
||||
let clone_args: CloneArgs = (&sync).into();
|
||||
clone_args.unique_path(&core_config().repo_directory)?
|
||||
};
|
||||
let file_path =
|
||||
file_path.parse::<PathBuf>().context("Invalid file path")?;
|
||||
let resource_path = resource_path
|
||||
.parse::<PathBuf>()
|
||||
.context("Invalid resource path")?;
|
||||
let full_path = root.join(&resource_path).join(&file_path);
|
||||
|
||||
if let Some(parent) = full_path.parent() {
|
||||
let _ = fs::create_dir_all(parent).await;
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
fs::write(&full_path, &contents).await.with_context(|| {
|
||||
format!("Failed to write file contents to {full_path:?}")
|
||||
})
|
||||
{
|
||||
update.push_error_log("Write file", format_serror(&e.into()));
|
||||
} else {
|
||||
update.push_simple_log(
|
||||
"Write file",
|
||||
format!("File written to {full_path:?}"),
|
||||
);
|
||||
};
|
||||
|
||||
if !all_logs_success(&update.logs) {
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
if sync.config.files_on_host {
|
||||
write_sync_file_contents_on_host(self, args, sync, update).await
|
||||
} else {
|
||||
write_sync_file_contents_git(self, args, sync, update).await
|
||||
if let Err(e) = (RefreshResourceSyncPending { sync: sync.name })
|
||||
.resolve(args)
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh failed",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
let commit_res = git::commit_file(
|
||||
&format!("{}: Commit Resource File", user.username),
|
||||
&root,
|
||||
&resource_path.join(&file_path),
|
||||
)
|
||||
.await;
|
||||
|
||||
update.logs.extend(commit_res.logs);
|
||||
|
||||
if let Err(e) = (RefreshResourceSyncPending { sync: sync.name })
|
||||
.resolve(args)
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh failed",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,7 +255,7 @@ impl Resolve<WriteArgs> for CommitSync {
|
||||
|
||||
let sync = resource::get_check_permissions::<
|
||||
entities::sync::ResourceSync,
|
||||
>(&self.sync, user, PermissionLevel::Write)
|
||||
>(&self.sync, &user, PermissionLevel::Write)
|
||||
.await?;
|
||||
|
||||
let file_contents_empty = sync.config.file_contents_empty();
|
||||
@@ -392,7 +304,7 @@ impl Resolve<WriteArgs> for CommitSync {
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut update = make_update(&sync, Operation::CommitSync, user);
|
||||
let mut update = make_update(&sync, Operation::CommitSync, &user);
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
update.logs.push(Log::simple("Resources", res.toml.clone()));
|
||||
@@ -407,9 +319,7 @@ impl Resolve<WriteArgs> for CommitSync {
|
||||
.join(to_komodo_name(&sync.name))
|
||||
.join(&resource_path);
|
||||
if let Some(parent) = file_path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize resource file parent directory {parent:?}"))?;
|
||||
let _ = tokio::fs::create_dir_all(&parent).await;
|
||||
};
|
||||
if let Err(e) = tokio::fs::write(&file_path, &res.toml)
|
||||
.await
|
||||
@@ -443,7 +353,6 @@ impl Resolve<WriteArgs> for CommitSync {
|
||||
&root,
|
||||
&resource_path,
|
||||
&res.toml,
|
||||
&sync.config.branch,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -489,6 +398,21 @@ impl Resolve<WriteArgs> for CommitSync {
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
// but will fail to update cache in that case.
|
||||
if let Ok(update_doc) = to_document(&update) {
|
||||
let _ = update_one_by_id(
|
||||
&db_client().updates,
|
||||
&update.id,
|
||||
mungos::update::Update::Set(update_doc),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
refresh_resource_sync_state_cache().await;
|
||||
}
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
@@ -539,9 +463,12 @@ impl Resolve<WriteArgs> for RefreshResourceSyncPending {
|
||||
sync.info.pending_message = message;
|
||||
|
||||
if !sync.info.remote_errors.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Remote resources have errors. Cannot compute diffs."
|
||||
));
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Remote resources have errors. Cannot compute diffs."
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let resources = resources?;
|
||||
|
||||
@@ -68,7 +68,7 @@ impl Resolve<WriteArgs> for RenameTag {
|
||||
return Err(anyhow!("tag name cannot be ObjectId").into());
|
||||
}
|
||||
|
||||
get_tag_check_owner(&self.id, user).await?;
|
||||
get_tag_check_owner(&self.id, &user).await?;
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().tags,
|
||||
@@ -89,7 +89,7 @@ impl Resolve<WriteArgs> for DeleteTag {
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<Tag> {
|
||||
let tag = get_tag_check_owner(&self.id, user).await?;
|
||||
let tag = get_tag_check_owner(&self.id, &user).await?;
|
||||
|
||||
tokio::try_join!(
|
||||
resource::remove_tag_from_all::<Server>(&self.id),
|
||||
|
||||
@@ -12,9 +12,7 @@ use komodo_client::{
|
||||
use mungos::mongodb::bson::{doc, oid::ObjectId};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
config::core_config, helpers::hash_password, state::db_client,
|
||||
};
|
||||
use crate::{helpers::hash_password, state::db_client};
|
||||
|
||||
use super::WriteArgs;
|
||||
|
||||
@@ -25,16 +23,6 @@ impl Resolve<WriteArgs> for UpdateUserUsername {
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<UpdateUserUsernameResponse> {
|
||||
for locked_username in &core_config().lock_login_credentials_for {
|
||||
if locked_username == "__ALL__"
|
||||
|| *locked_username == user.username
|
||||
{
|
||||
return Err(
|
||||
anyhow!("User not allowed to update their username.")
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if self.username.is_empty() {
|
||||
return Err(anyhow!("Username cannot be empty.").into());
|
||||
}
|
||||
@@ -68,16 +56,6 @@ impl Resolve<WriteArgs> for UpdateUserPassword {
|
||||
self,
|
||||
WriteArgs { user }: &WriteArgs,
|
||||
) -> serror::Result<UpdateUserPasswordResponse> {
|
||||
for locked_username in &core_config().lock_login_credentials_for {
|
||||
if locked_username == "__ALL__"
|
||||
|| *locked_username == user.username
|
||||
{
|
||||
return Err(
|
||||
anyhow!("User not allowed to update their password.")
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
let UserConfig::Local { .. } = user.config else {
|
||||
return Err(anyhow!("User is not local user").into());
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ impl Resolve<WriteArgs> for CreateVariable {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::CreateVariable,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
update
|
||||
@@ -92,7 +92,7 @@ impl Resolve<WriteArgs> for UpdateVariableValue {
|
||||
let mut update = make_update(
|
||||
ResourceTarget::system(),
|
||||
Operation::UpdateVariableValue,
|
||||
user,
|
||||
&user,
|
||||
);
|
||||
|
||||
let log = if variable.is_secret {
|
||||
|
||||
@@ -2,37 +2,17 @@ use std::sync::OnceLock;
|
||||
|
||||
use anyhow::Context;
|
||||
use openidconnect::{
|
||||
core::*, Client, ClientId, ClientSecret, EmptyAdditionalClaims,
|
||||
EndpointMaybeSet, EndpointNotSet, EndpointSet, IssuerUrl,
|
||||
RedirectUrl, StandardErrorResponse,
|
||||
core::{CoreClient, CoreProviderMetadata},
|
||||
reqwest::async_http_client,
|
||||
ClientId, ClientSecret, IssuerUrl, RedirectUrl,
|
||||
};
|
||||
|
||||
use crate::config::core_config;
|
||||
|
||||
type OidcClient = Client<
|
||||
EmptyAdditionalClaims,
|
||||
CoreAuthDisplay,
|
||||
CoreGenderClaim,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJsonWebKey,
|
||||
CoreAuthPrompt,
|
||||
StandardErrorResponse<CoreErrorResponseType>,
|
||||
CoreTokenResponse,
|
||||
CoreTokenIntrospectionResponse,
|
||||
CoreRevocableToken,
|
||||
CoreRevocationErrorResponse,
|
||||
EndpointSet,
|
||||
EndpointNotSet,
|
||||
EndpointNotSet,
|
||||
EndpointNotSet,
|
||||
EndpointMaybeSet,
|
||||
EndpointMaybeSet,
|
||||
>;
|
||||
|
||||
static DEFAULT_OIDC_CLIENT: OnceLock<Option<OidcClient>> =
|
||||
static DEFAULT_OIDC_CLIENT: OnceLock<Option<CoreClient>> =
|
||||
OnceLock::new();
|
||||
|
||||
pub fn default_oidc_client() -> Option<&'static OidcClient> {
|
||||
pub fn default_oidc_client() -> Option<&'static CoreClient> {
|
||||
DEFAULT_OIDC_CLIENT
|
||||
.get()
|
||||
.expect("OIDC client get before init")
|
||||
@@ -55,7 +35,7 @@ pub async fn init_default_oidc_client() {
|
||||
// Use OpenID Connect Discovery to fetch the provider metadata.
|
||||
let provider_metadata = CoreProviderMetadata::discover_async(
|
||||
IssuerUrl::new(config.oidc_provider.clone())?,
|
||||
super::reqwest_client(),
|
||||
async_http_client,
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
|
||||
@@ -12,8 +12,9 @@ use komodo_client::entities::{
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, Document};
|
||||
use openidconnect::{
|
||||
core::CoreAuthenticationFlow, AuthorizationCode, CsrfToken, Nonce,
|
||||
Scope, TokenResponse,
|
||||
core::CoreAuthenticationFlow, AccessTokenHash, AuthorizationCode,
|
||||
CsrfToken, Nonce, OAuth2TokenResponse, PkceCodeChallenge,
|
||||
PkceCodeVerifier, Scope, TokenResponse,
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use serde::Deserialize;
|
||||
@@ -28,22 +29,13 @@ use super::RedirectQuery;
|
||||
|
||||
pub mod client;
|
||||
|
||||
fn reqwest_client() -> &'static reqwest::Client {
|
||||
static REQWEST: OnceLock<reqwest::Client> = OnceLock::new();
|
||||
REQWEST.get_or_init(|| {
|
||||
reqwest::Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
.expect("Invalid OIDC reqwest client")
|
||||
})
|
||||
}
|
||||
|
||||
/// CSRF tokens can only be used once from the callback,
|
||||
/// and must be used within this timeframe
|
||||
const CSRF_VALID_FOR_MS: i64 = 120_000; // 2 minutes for user to log in.
|
||||
|
||||
type RedirectUrl = Option<String>;
|
||||
type CsrfMap = DashMap<String, (Nonce, RedirectUrl, i64)>;
|
||||
type CsrfMap =
|
||||
DashMap<String, (PkceCodeVerifier, Nonce, RedirectUrl, i64)>;
|
||||
fn csrf_verifier_tokens() -> &'static CsrfMap {
|
||||
static CSRF: OnceLock<CsrfMap> = OnceLock::new();
|
||||
CSRF.get_or_init(Default::default)
|
||||
@@ -72,6 +64,10 @@ async fn login(
|
||||
let client =
|
||||
default_oidc_client().context("OIDC Client not configured")?;
|
||||
|
||||
// Generate a PKCE challenge.
|
||||
let (pkce_challenge, pkce_verifier) =
|
||||
PkceCodeChallenge::new_random_sha256();
|
||||
|
||||
// Generate the authorization URL.
|
||||
let (auth_url, csrf_token, nonce) = client
|
||||
.authorize_url(
|
||||
@@ -81,12 +77,18 @@ async fn login(
|
||||
)
|
||||
.add_scope(Scope::new("openid".to_string()))
|
||||
.add_scope(Scope::new("email".to_string()))
|
||||
.set_pkce_challenge(pkce_challenge)
|
||||
.url();
|
||||
|
||||
// Data inserted here will be matched on callback side for csrf protection.
|
||||
csrf_verifier_tokens().insert(
|
||||
csrf_token.secret().clone(),
|
||||
(nonce, redirect, komodo_timestamp() + CSRF_VALID_FOR_MS),
|
||||
(
|
||||
pkce_verifier,
|
||||
nonce,
|
||||
redirect,
|
||||
komodo_timestamp() + CSRF_VALID_FOR_MS,
|
||||
),
|
||||
);
|
||||
|
||||
let config = core_config();
|
||||
@@ -133,9 +135,10 @@ async fn callback(
|
||||
query.state.context("Provider did not return state")?,
|
||||
);
|
||||
|
||||
let (_, (nonce, redirect, valid_until)) = csrf_verifier_tokens()
|
||||
.remove(state.secret())
|
||||
.context("CSRF Token invalid")?;
|
||||
let (_, (pkce_verifier, nonce, redirect, valid_until)) =
|
||||
csrf_verifier_tokens()
|
||||
.remove(state.secret())
|
||||
.context("CSRF Token invalid")?;
|
||||
|
||||
if komodo_timestamp() > valid_until {
|
||||
return Err(anyhow!(
|
||||
@@ -145,8 +148,9 @@ async fn callback(
|
||||
|
||||
let token_response = client
|
||||
.exchange_code(AuthorizationCode::new(code))
|
||||
.context("Failed to get Oauth token at exchange code")?
|
||||
.request_async(reqwest_client())
|
||||
// Set the PKCE code verifier.
|
||||
.set_pkce_verifier(pkce_verifier)
|
||||
.request_async(openidconnect::reqwest::async_http_client)
|
||||
.await
|
||||
.context("Failed to get Oauth token")?;
|
||||
|
||||
@@ -171,6 +175,19 @@ async fn callback(
|
||||
.claims(&verifier, &nonce)
|
||||
.context("Failed to verify token claims")?;
|
||||
|
||||
// Verify the access token hash to ensure that the access token hasn't been substituted for
|
||||
// another user's.
|
||||
if let Some(expected_access_token_hash) = claims.access_token_hash()
|
||||
{
|
||||
let actual_access_token_hash = AccessTokenHash::from_token(
|
||||
token_response.access_token(),
|
||||
&id_token.signing_alg()?,
|
||||
)?;
|
||||
if actual_access_token_hash != *expected_access_token_hash {
|
||||
return Err(anyhow!("Invalid access token"));
|
||||
}
|
||||
}
|
||||
|
||||
let user_id = claims.subject().as_str();
|
||||
|
||||
let db_client = db_client();
|
||||
|
||||
@@ -182,8 +182,6 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
.unwrap_or(config.disable_user_registration),
|
||||
disable_non_admin_create: env.komodo_disable_non_admin_create
|
||||
.unwrap_or(config.disable_non_admin_create),
|
||||
lock_login_credentials_for: env.komodo_lock_login_credentials_for
|
||||
.unwrap_or(config.lock_login_credentials_for),
|
||||
local_auth: env.komodo_local_auth
|
||||
.unwrap_or(config.local_auth),
|
||||
logging: LogConfig {
|
||||
|
||||
@@ -84,8 +84,8 @@ pub struct UpdateGuard<'a, States: Default + Send + 'static>(
|
||||
&'a Mutex<States>,
|
||||
);
|
||||
|
||||
impl<States: Default + Send + 'static> Drop
|
||||
for UpdateGuard<'_, States>
|
||||
impl<'a, States: Default + Send + 'static> Drop
|
||||
for UpdateGuard<'a, States>
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
let mut lock = match self.0.lock() {
|
||||
|
||||
@@ -21,7 +21,7 @@ use mungos::{
|
||||
mongodb::bson::{doc, oid::ObjectId, to_document, Bson},
|
||||
};
|
||||
use periphery_client::PeripheryClient;
|
||||
use rand::Rng;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
@@ -54,8 +54,8 @@ pub fn empty_or_only_spaces(word: &str) -> bool {
|
||||
}
|
||||
|
||||
pub fn random_string(length: usize) -> String {
|
||||
rand::rng()
|
||||
.sample_iter(&rand::distr::Alphanumeric)
|
||||
thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(length)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
|
||||
@@ -1259,7 +1259,7 @@ impl ExtendBatch for BatchDeployStack {
|
||||
fn single_execution(stack: String) -> Execution {
|
||||
Execution::DeployStack(DeployStack {
|
||||
stack,
|
||||
services: Vec::new(),
|
||||
service: None,
|
||||
stop_time: None,
|
||||
})
|
||||
}
|
||||
@@ -1280,7 +1280,7 @@ impl ExtendBatch for BatchDestroyStack {
|
||||
fn single_execution(stack: String) -> Execution {
|
||||
Execution::DestroyStack(DestroyStack {
|
||||
stack,
|
||||
services: Vec::new(),
|
||||
service: None,
|
||||
remove_orphans: false,
|
||||
stop_time: None,
|
||||
})
|
||||
|
||||
@@ -34,9 +34,8 @@ async fn prune_images() -> anyhow::Result<()> {
|
||||
.await
|
||||
.context("failed to get servers from db")?
|
||||
.into_iter()
|
||||
.filter(|server| {
|
||||
server.config.enabled && server.config.auto_prune
|
||||
})
|
||||
// This could be done in the mongo query, but rather have rust type system guarantee this.
|
||||
.filter(|server| server.config.auto_prune)
|
||||
.map(|server| async move {
|
||||
(
|
||||
async {
|
||||
|
||||
@@ -405,7 +405,7 @@ pub async fn init_execution_update(
|
||||
|
||||
// Stack
|
||||
ExecuteRequest::DeployStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::DeployStackService
|
||||
} else {
|
||||
Operation::DeployStack
|
||||
@@ -427,7 +427,7 @@ pub async fn init_execution_update(
|
||||
return Ok(Default::default())
|
||||
}
|
||||
ExecuteRequest::StartStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::StartStackService
|
||||
} else {
|
||||
Operation::StartStack
|
||||
@@ -437,7 +437,7 @@ pub async fn init_execution_update(
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PullStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::PullStackService
|
||||
} else {
|
||||
Operation::PullStack
|
||||
@@ -447,7 +447,7 @@ pub async fn init_execution_update(
|
||||
),
|
||||
),
|
||||
ExecuteRequest::RestartStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::RestartStackService
|
||||
} else {
|
||||
Operation::RestartStack
|
||||
@@ -457,7 +457,7 @@ pub async fn init_execution_update(
|
||||
),
|
||||
),
|
||||
ExecuteRequest::PauseStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::PauseStackService
|
||||
} else {
|
||||
Operation::PauseStack
|
||||
@@ -467,7 +467,7 @@ pub async fn init_execution_update(
|
||||
),
|
||||
),
|
||||
ExecuteRequest::UnpauseStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::UnpauseStackService
|
||||
} else {
|
||||
Operation::UnpauseStack
|
||||
@@ -477,7 +477,7 @@ pub async fn init_execution_update(
|
||||
),
|
||||
),
|
||||
ExecuteRequest::StopStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::StopStackService
|
||||
} else {
|
||||
Operation::StopStack
|
||||
@@ -487,7 +487,7 @@ pub async fn init_execution_update(
|
||||
),
|
||||
),
|
||||
ExecuteRequest::DestroyStack(data) => (
|
||||
if !data.services.is_empty() {
|
||||
if data.service.is_some() {
|
||||
Operation::DestroyStackService
|
||||
} else {
|
||||
Operation::DestroyStack
|
||||
|
||||
@@ -231,7 +231,7 @@ impl StackExecution for DeployStack {
|
||||
if stack.config.webhook_force_deploy {
|
||||
let req = ExecuteRequest::DeployStack(DeployStack {
|
||||
stack: stack.id,
|
||||
services: Vec::new(),
|
||||
service: None,
|
||||
stop_time: None,
|
||||
});
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
|
||||
@@ -59,6 +59,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
resource::spawn_repo_state_refresh_loop();
|
||||
resource::spawn_procedure_state_refresh_loop();
|
||||
resource::spawn_action_state_refresh_loop();
|
||||
resource::spawn_resource_sync_state_refresh_loop();
|
||||
helpers::prune::spawn_prune_loop();
|
||||
|
||||
// Setup static frontend services
|
||||
|
||||
@@ -322,8 +322,8 @@ pub async fn update_stack_cache(
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let mut update_available = false;
|
||||
let mut images_with_update = Vec::new();
|
||||
let mut services_to_update = Vec::new();
|
||||
|
||||
for service in services_with_containers.iter() {
|
||||
if service.update_available {
|
||||
@@ -336,7 +336,7 @@ pub async fn update_stack_cache(
|
||||
.map(|c| c.state == ContainerStateStatusEnum::Running)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
services_to_update.push(service.service.clone());
|
||||
update_available = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,7 +346,7 @@ pub async fn update_stack_cache(
|
||||
&services,
|
||||
containers,
|
||||
);
|
||||
if !services_to_update.is_empty()
|
||||
if update_available
|
||||
&& stack.config.auto_update
|
||||
&& state == StackState::Running
|
||||
&& !action_states()
|
||||
@@ -358,16 +358,11 @@ pub async fn update_stack_cache(
|
||||
{
|
||||
let id = stack.id.clone();
|
||||
let server_name = server_name.clone();
|
||||
let services = if stack.config.auto_update_all_services {
|
||||
Vec::new()
|
||||
} else {
|
||||
services_to_update
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
match execute::inner_handler(
|
||||
ExecuteRequest::DeployStack(DeployStack {
|
||||
stack: stack.name.clone(),
|
||||
services,
|
||||
service: None,
|
||||
stop_time: None,
|
||||
}),
|
||||
auto_redeploy_user().to_owned(),
|
||||
|
||||
@@ -73,6 +73,10 @@ pub use refresh::spawn_resource_refresh_loop;
|
||||
pub use repo::{
|
||||
refresh_repo_state_cache, spawn_repo_state_refresh_loop,
|
||||
};
|
||||
pub use sync::{
|
||||
refresh_resource_sync_state_cache,
|
||||
spawn_resource_sync_state_refresh_loop,
|
||||
};
|
||||
|
||||
/// Implement on each Komodo resource for common methods
|
||||
pub trait KomodoResource {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
@@ -17,12 +19,15 @@ use komodo_client::{
|
||||
},
|
||||
};
|
||||
use mongo_indexed::doc;
|
||||
use mungos::mongodb::Collection;
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{options::FindOneOptions, Collection},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::write::WriteArgs,
|
||||
state::{action_states, db_client},
|
||||
state::{action_states, db_client, resource_sync_state_cache},
|
||||
};
|
||||
|
||||
impl super::KomodoResource for ResourceSync {
|
||||
@@ -112,6 +117,7 @@ impl super::KomodoResource for ResourceSync {
|
||||
format_serror(&e.error.context("The sync pending cache has failed to refresh. This is likely due to a misconfiguration of the sync").into())
|
||||
);
|
||||
};
|
||||
refresh_resource_sync_state_cache().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -174,6 +180,35 @@ impl super::KomodoResource for ResourceSync {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_resource_sync_state_refresh_loop() {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
refresh_resource_sync_state_cache().await;
|
||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn refresh_resource_sync_state_cache() {
|
||||
let _ = async {
|
||||
let resource_syncs =
|
||||
find_collect(&db_client().resource_syncs, None, None)
|
||||
.await
|
||||
.context("failed to get resource_syncs from db")?;
|
||||
let cache = resource_sync_state_cache();
|
||||
for resource_sync in resource_syncs {
|
||||
let state =
|
||||
get_resource_sync_state_from_db(&resource_sync.id).await;
|
||||
cache.insert(resource_sync.id, state).await;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!("failed to refresh resource_sync state cache | {e:#}")
|
||||
});
|
||||
}
|
||||
|
||||
async fn get_resource_sync_state(
|
||||
id: &String,
|
||||
data: &ResourceSyncInfo,
|
||||
@@ -197,15 +232,57 @@ async fn get_resource_sync_state(
|
||||
{
|
||||
return state;
|
||||
}
|
||||
if data.pending_error.is_some() || !data.remote_errors.is_empty() {
|
||||
ResourceSyncState::Failed
|
||||
} else if !data.resource_updates.is_empty()
|
||||
if data.pending_error.is_some() {
|
||||
return ResourceSyncState::Failed;
|
||||
}
|
||||
if !data.resource_updates.is_empty()
|
||||
|| !data.variable_updates.is_empty()
|
||||
|| !data.user_group_updates.is_empty()
|
||||
|| data.pending_deploy.to_deploy > 0
|
||||
{
|
||||
ResourceSyncState::Pending
|
||||
} else {
|
||||
ResourceSyncState::Ok
|
||||
return ResourceSyncState::Pending;
|
||||
}
|
||||
resource_sync_state_cache()
|
||||
.get(id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn get_resource_sync_state_from_db(
|
||||
id: &str,
|
||||
) -> ResourceSyncState {
|
||||
async {
|
||||
let state = db_client()
|
||||
.updates
|
||||
.find_one(doc! {
|
||||
"target.type": "ResourceSync",
|
||||
"target.id": id,
|
||||
"$or": [
|
||||
{ "operation": "RunSync" },
|
||||
{ "operation": "CommitSync" },
|
||||
],
|
||||
})
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
)
|
||||
.await?
|
||||
.map(|u| {
|
||||
if u.success {
|
||||
ResourceSyncState::Ok
|
||||
} else {
|
||||
ResourceSyncState::Failed
|
||||
}
|
||||
})
|
||||
.unwrap_or(ResourceSyncState::Ok);
|
||||
anyhow::Ok(state)
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!(
|
||||
"failed to get resource sync state from db for {id} | {e:#}"
|
||||
)
|
||||
})
|
||||
.unwrap_or(ResourceSyncState::Unknown)
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ pub trait ExecuteCompose {
|
||||
async fn execute(
|
||||
periphery: PeripheryClient,
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
extras: Self::Extras,
|
||||
) -> anyhow::Result<Log>;
|
||||
}
|
||||
|
||||
pub async fn execute_compose<T: ExecuteCompose>(
|
||||
stack: &str,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
user: &User,
|
||||
set_in_progress: impl Fn(&mut StackActionState),
|
||||
mut update: Update,
|
||||
@@ -53,19 +53,16 @@ pub async fn execute_compose<T: ExecuteCompose>(
|
||||
|
||||
let periphery = periphery_client(&server)?;
|
||||
|
||||
if !services.is_empty() {
|
||||
if let Some(service) = &service {
|
||||
update.logs.push(Log::simple(
|
||||
"Service/s",
|
||||
format!(
|
||||
"Execution requested for Stack service/s {}",
|
||||
services.join(", ")
|
||||
),
|
||||
&format!("Service: {service}"),
|
||||
format!("Execution requested for Stack service {service}"),
|
||||
))
|
||||
}
|
||||
|
||||
update
|
||||
.logs
|
||||
.push(T::execute(periphery, stack, services, extras).await?);
|
||||
.push(T::execute(periphery, stack, service, extras).await?);
|
||||
|
||||
// Ensure cached stack state up to date by updating server cache
|
||||
update_cache_for_server(&server).await;
|
||||
@@ -81,18 +78,16 @@ impl ExecuteCompose for StartStack {
|
||||
async fn execute(
|
||||
periphery: PeripheryClient,
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
_: Self::Extras,
|
||||
) -> anyhow::Result<Log> {
|
||||
let service_args = if !services.is_empty() {
|
||||
services.join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let service = service
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
periphery
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: format!("start{service_args}"),
|
||||
command: format!("start{service}"),
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -103,18 +98,16 @@ impl ExecuteCompose for RestartStack {
|
||||
async fn execute(
|
||||
periphery: PeripheryClient,
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
_: Self::Extras,
|
||||
) -> anyhow::Result<Log> {
|
||||
let service_args = if !services.is_empty() {
|
||||
services.join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let service = service
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
periphery
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: format!("restart{service_args}"),
|
||||
command: format!("restart{service}"),
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -125,18 +118,16 @@ impl ExecuteCompose for PauseStack {
|
||||
async fn execute(
|
||||
periphery: PeripheryClient,
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
_: Self::Extras,
|
||||
) -> anyhow::Result<Log> {
|
||||
let service_args = if !services.is_empty() {
|
||||
services.join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let service = service
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
periphery
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: format!("pause{service_args}"),
|
||||
command: format!("pause{service}"),
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -147,18 +138,16 @@ impl ExecuteCompose for UnpauseStack {
|
||||
async fn execute(
|
||||
periphery: PeripheryClient,
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
_: Self::Extras,
|
||||
) -> anyhow::Result<Log> {
|
||||
let service_args = if !services.is_empty() {
|
||||
services.join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let service = service
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
periphery
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: format!("unpause{service_args}"),
|
||||
command: format!("unpause{service}"),
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -169,19 +158,17 @@ impl ExecuteCompose for StopStack {
|
||||
async fn execute(
|
||||
periphery: PeripheryClient,
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
timeout: Self::Extras,
|
||||
) -> anyhow::Result<Log> {
|
||||
let service_args = if !services.is_empty() {
|
||||
services.join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let service = service
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
let maybe_timeout = maybe_timeout(timeout);
|
||||
periphery
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: format!("stop{maybe_timeout}{service_args}"),
|
||||
command: format!("stop{maybe_timeout}{service}"),
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -192,14 +179,12 @@ impl ExecuteCompose for DestroyStack {
|
||||
async fn execute(
|
||||
periphery: PeripheryClient,
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
(timeout, remove_orphans): Self::Extras,
|
||||
) -> anyhow::Result<Log> {
|
||||
let service_args = if !services.is_empty() {
|
||||
services.join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let service = service
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
let maybe_timeout = maybe_timeout(timeout);
|
||||
let maybe_remove_orphans = if remove_orphans {
|
||||
" --remove-orphans"
|
||||
@@ -210,7 +195,7 @@ impl ExecuteCompose for DestroyStack {
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: format!(
|
||||
"down{maybe_timeout}{maybe_remove_orphans}{service_args}"
|
||||
"down{maybe_timeout}{maybe_remove_orphans}{service}"
|
||||
),
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -12,6 +12,7 @@ use komodo_client::entities::{
|
||||
procedure::ProcedureState,
|
||||
repo::RepoState,
|
||||
stack::StackState,
|
||||
sync::ResourceSyncState,
|
||||
};
|
||||
use octorust::auth::{
|
||||
Credentials, InstallationTokenGenerator, JWTCredentials,
|
||||
@@ -196,3 +197,12 @@ pub fn action_state_cache() -> &'static ActionStateCache {
|
||||
OnceLock::new();
|
||||
ACTION_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub type ResourceSyncStateCache = Cache<String, ResourceSyncState>;
|
||||
|
||||
pub fn resource_sync_state_cache() -> &'static ResourceSyncStateCache
|
||||
{
|
||||
static RESOURCE_SYNC_STATE_CACHE: OnceLock<ResourceSyncStateCache> =
|
||||
OnceLock::new();
|
||||
RESOURCE_SYNC_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ pub async fn deploy_from_cache(
|
||||
ResourceTarget::Stack(name) => {
|
||||
let req = ExecuteRequest::DeployStack(DeployStack {
|
||||
stack: name.to_string(),
|
||||
services: Vec::new(),
|
||||
service: None,
|
||||
stop_time: None,
|
||||
});
|
||||
|
||||
|
||||
@@ -165,7 +165,6 @@ fn read_resource_file(
|
||||
}
|
||||
|
||||
/// Reads down into directories.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn read_resources_directory(
|
||||
root_path: &Path,
|
||||
// relative to root path.
|
||||
|
||||
@@ -223,26 +223,13 @@ impl AllResourcesById {
|
||||
fn deserialize_resources_toml(
|
||||
toml_str: &str,
|
||||
) -> anyhow::Result<ResourcesToml> {
|
||||
::toml::from_str::<ResourcesToml>(&escape_between_triple_string(
|
||||
toml_str,
|
||||
))
|
||||
::toml::from_str::<ResourcesToml>(
|
||||
&toml_str
|
||||
// Escape the \ for the user on incoming.
|
||||
// After deserialized by `toml` crate, the contents
|
||||
// will match what would be passed on eg command line exactly.
|
||||
.replace(r#"\"#, r#"\\"#),
|
||||
)
|
||||
// the error without this comes through with multiple lines (\n) and looks bad
|
||||
.map_err(|e| anyhow!("{e:#}"))
|
||||
}
|
||||
|
||||
fn escape_between_triple_string(toml_str: &str) -> String {
|
||||
toml_str
|
||||
.split(r#"""""#)
|
||||
.enumerate()
|
||||
.map(|(i, section)| {
|
||||
// The odd entries are between triple string,
|
||||
// and the \ need to be escaped.
|
||||
if i % 2 == 0 {
|
||||
section.to_string()
|
||||
} else {
|
||||
section.replace(r#"\"#, r#"\\"#)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(r#"""""#)
|
||||
}
|
||||
|
||||
@@ -133,9 +133,9 @@ pub async fn get_remote_resources(
|
||||
format!("Failed to update resource repo at {repo_path:?}")
|
||||
})?;
|
||||
|
||||
// let hash = hash.context("failed to get commit hash")?;
|
||||
// let message =
|
||||
// message.context("failed to get commit hash message")?;
|
||||
let hash = hash.context("failed to get commit hash")?;
|
||||
let message =
|
||||
message.context("failed to get commit hash message")?;
|
||||
|
||||
let (mut files, mut file_errors) = (Vec::new(), Vec::new());
|
||||
let resources = super::file::read_resources(
|
||||
@@ -152,7 +152,7 @@ pub async fn get_remote_resources(
|
||||
files,
|
||||
file_errors,
|
||||
logs,
|
||||
hash,
|
||||
message,
|
||||
hash: Some(hash),
|
||||
message: Some(message),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -188,7 +188,6 @@ impl ToToml for Stack {
|
||||
config
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
#[allow(clippy::single_match)]
|
||||
match key.as_str() {
|
||||
"server_id" => return Ok((String::from("server"), value)),
|
||||
_ => {}
|
||||
|
||||
@@ -12,7 +12,6 @@ use partial_derive2::MaybeNone;
|
||||
|
||||
use super::{AllResourcesById, ResourceSyncTrait};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn push_updates_for_view<Resource: ResourceSyncTrait>(
|
||||
resources: Vec<ResourceToml<Resource::PartialConfig>>,
|
||||
delete: bool,
|
||||
|
||||
@@ -35,7 +35,6 @@ svi.workspace = true
|
||||
# external
|
||||
axum-server.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
futures.workspace = true
|
||||
tracing.workspace = true
|
||||
bollard.workspace = true
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use command::{
|
||||
run_komodo_command, run_komodo_command_with_interpolation,
|
||||
};
|
||||
use command::run_komodo_command;
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
entities::{
|
||||
@@ -69,7 +67,7 @@ impl Resolve<super::Args> for build::Build {
|
||||
Ok(should_push) => should_push,
|
||||
Err(e) => {
|
||||
logs.push(Log::error(
|
||||
"Docker Login",
|
||||
"docker login",
|
||||
format_serror(
|
||||
&e.context("failed to login to docker registry").into(),
|
||||
),
|
||||
@@ -124,23 +122,40 @@ impl Resolve<super::Args> for build::Build {
|
||||
|
||||
if *skip_secret_interp {
|
||||
let build_log = run_komodo_command(
|
||||
"Docker Build",
|
||||
"docker build",
|
||||
build_dir.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
logs.push(build_log);
|
||||
} else if let Some(log) = run_komodo_command_with_interpolation(
|
||||
"Docker Build",
|
||||
build_dir.as_ref(),
|
||||
command,
|
||||
false,
|
||||
&periphery_config().secrets,
|
||||
&core_replacers,
|
||||
)
|
||||
.await
|
||||
{
|
||||
logs.push(log)
|
||||
} else {
|
||||
// Interpolate any missing secrets
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context(
|
||||
"failed to interpolate secrets into docker build command",
|
||||
)?;
|
||||
replacers.extend(core_replacers);
|
||||
|
||||
let mut build_log = run_komodo_command(
|
||||
"docker build",
|
||||
build_dir.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
build_log.command =
|
||||
svi::replace_in_string(&build_log.command, &replacers);
|
||||
build_log.stdout =
|
||||
svi::replace_in_string(&build_log.stdout, &replacers);
|
||||
build_log.stderr =
|
||||
svi::replace_in_string(&build_log.stderr, &replacers);
|
||||
logs.push(build_log);
|
||||
}
|
||||
|
||||
cleanup_secret_env_vars(&secret_args);
|
||||
@@ -235,7 +250,10 @@ impl Resolve<super::Args> for PruneBuilders {
|
||||
#[instrument(name = "PruneBuilders", skip_all)]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = String::from("docker builder prune -a -f");
|
||||
Ok(run_komodo_command("Prune Builders", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune builders", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +263,6 @@ impl Resolve<super::Args> for PruneBuildx {
|
||||
#[instrument(name = "PruneBuildx", skip_all)]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = String::from("docker buildx prune -a -f");
|
||||
Ok(run_komodo_command("Prune Buildx", None, command).await)
|
||||
Ok(run_komodo_command("prune buildx", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ impl Resolve<super::Args> for ListComposeProjects {
|
||||
) -> serror::Result<Vec<ComposeProject>> {
|
||||
let docker_compose = docker_compose();
|
||||
let res = run_komodo_command(
|
||||
"List Projects",
|
||||
"list projects",
|
||||
None,
|
||||
format!("{docker_compose} ls --all --format json"),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -37,8 +38,8 @@ impl Resolve<super::Args> for ListComposeProjects {
|
||||
return Err(
|
||||
anyhow!("{}", res.combined())
|
||||
.context(format!(
|
||||
"failed to list compose projects using {docker_compose} ls"
|
||||
))
|
||||
"failed to list compose projects using {docker_compose} ls"
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
@@ -81,12 +82,12 @@ pub struct DockerComposeLsItem {
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for GetComposeLog {
|
||||
#[instrument(name = "GetComposeLog", level = "debug")]
|
||||
impl Resolve<super::Args> for GetComposeServiceLog {
|
||||
#[instrument(name = "GetComposeServiceLog", level = "debug")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let GetComposeLog {
|
||||
let GetComposeServiceLog {
|
||||
project,
|
||||
services,
|
||||
service,
|
||||
tail,
|
||||
timestamps,
|
||||
} = self;
|
||||
@@ -94,19 +95,20 @@ impl Resolve<super::Args> for GetComposeLog {
|
||||
let timestamps =
|
||||
timestamps.then_some(" --timestamps").unwrap_or_default();
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project} logs --tail {tail}{timestamps} {}",
|
||||
services.join(" ")
|
||||
"{docker_compose} -p {project} logs {service} --tail {tail}{timestamps}"
|
||||
);
|
||||
Ok(run_komodo_command("get stack log", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("get stack log", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<super::Args> for GetComposeLogSearch {
|
||||
#[instrument(name = "GetComposeLogSearch", level = "debug")]
|
||||
impl Resolve<super::Args> for GetComposeServiceLogSearch {
|
||||
#[instrument(name = "GetComposeServiceLogSearch", level = "debug")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let GetComposeLogSearch {
|
||||
let GetComposeServiceLogSearch {
|
||||
project,
|
||||
services,
|
||||
service,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
@@ -116,11 +118,11 @@ impl Resolve<super::Args> for GetComposeLogSearch {
|
||||
let grep = log_grep(&terms, combinator, invert);
|
||||
let timestamps =
|
||||
timestamps.then_some(" --timestamps").unwrap_or_default();
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project} logs --tail 5000{timestamps} {} 2>&1 | {grep}",
|
||||
services.join(" ")
|
||||
);
|
||||
Ok(run_komodo_command("Get stack log grep", None, command).await)
|
||||
let command = format!("{docker_compose} -p {project} logs {service} --tail 5000{timestamps} 2>&1 | {grep}");
|
||||
Ok(
|
||||
run_komodo_command("get stack log grep", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,15 +181,7 @@ impl Resolve<super::Args> for GetComposeContentsOnHost {
|
||||
//
|
||||
|
||||
impl Resolve<super::Args> for WriteComposeContentsToHost {
|
||||
#[instrument(
|
||||
name = "WriteComposeContentsToHost",
|
||||
skip_all,
|
||||
fields(
|
||||
stack = &self.name,
|
||||
run_directory = &self.run_directory,
|
||||
file_path = &self.file_path,
|
||||
)
|
||||
)]
|
||||
#[instrument(name = "WriteComposeContentsToHost")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let WriteComposeContentsToHost {
|
||||
name,
|
||||
@@ -204,9 +198,7 @@ impl Resolve<super::Args> for WriteComposeContentsToHost {
|
||||
.collect::<PathBuf>();
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = file_path.parent() {
|
||||
fs::create_dir_all(&parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize environment file parent directory {parent:?}"))?;
|
||||
let _ = fs::create_dir_all(&parent).await;
|
||||
}
|
||||
fs::write(&file_path, contents).await.with_context(|| {
|
||||
format!(
|
||||
@@ -264,14 +256,7 @@ impl Resolve<super::Args> for WriteCommitComposeContents {
|
||||
hash,
|
||||
message,
|
||||
..
|
||||
} = write_commit_file(
|
||||
&msg,
|
||||
&root,
|
||||
&file_path,
|
||||
&contents,
|
||||
&stack.config.branch,
|
||||
)
|
||||
.await?;
|
||||
} = write_commit_file(&msg, &root, &file_path, &contents).await?;
|
||||
|
||||
Ok(RepoActionResponse {
|
||||
logs,
|
||||
@@ -284,7 +269,7 @@ impl Resolve<super::Args> for WriteCommitComposeContents {
|
||||
|
||||
//
|
||||
|
||||
impl WriteStackRes for &mut ComposePullResponse {
|
||||
impl<'a> WriteStackRes for &'a mut ComposePullResponse {
|
||||
fn logs(&mut self) -> &mut Vec<Log> {
|
||||
&mut self.logs
|
||||
}
|
||||
@@ -296,7 +281,7 @@ impl Resolve<super::Args> for ComposePull {
|
||||
skip_all,
|
||||
fields(
|
||||
stack = &self.stack.name,
|
||||
services = format!("{:?}", self.services),
|
||||
service = &self.service,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -305,23 +290,13 @@ impl Resolve<super::Args> for ComposePull {
|
||||
) -> serror::Result<ComposePullResponse> {
|
||||
let ComposePull {
|
||||
stack,
|
||||
services,
|
||||
service,
|
||||
git_token,
|
||||
registry_token,
|
||||
} = self;
|
||||
let mut res = ComposePullResponse::default();
|
||||
|
||||
let (run_directory, env_file_path, _replacers) =
|
||||
match write_stack(&stack, git_token, &mut res).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
res.logs.push(Log::error(
|
||||
"Write Stack",
|
||||
format_serror(&e.into()),
|
||||
));
|
||||
return Ok(res);
|
||||
}
|
||||
};
|
||||
let (run_directory, env_file_path) =
|
||||
write_stack(&stack, git_token, &mut res).await?;
|
||||
|
||||
// Canonicalize the path to ensure it exists, and is the cleanest path to the run directory.
|
||||
let run_directory = run_directory.canonicalize().context(
|
||||
@@ -347,12 +322,10 @@ impl Resolve<super::Args> for ComposePull {
|
||||
}
|
||||
|
||||
let docker_compose = docker_compose();
|
||||
|
||||
let service_args = if services.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {}", services.join(" "))
|
||||
};
|
||||
let service_arg = service
|
||||
.as_ref()
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
let file_args = if stack.config.file_paths.is_empty() {
|
||||
String::from("compose.yaml")
|
||||
@@ -396,11 +369,12 @@ impl Resolve<super::Args> for ComposePull {
|
||||
let project_name = stack.project_name(false);
|
||||
|
||||
let log = run_komodo_command(
|
||||
"Compose Pull",
|
||||
"compose pull",
|
||||
run_directory.as_ref(),
|
||||
format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{additional_env_files}{env_file} pull{service_args}",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{additional_env_files}{env_file} pull{service_arg}",
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -418,7 +392,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
skip_all,
|
||||
fields(
|
||||
stack = &self.stack.name,
|
||||
services = format!("{:?}", self.services),
|
||||
service = &self.service,
|
||||
)
|
||||
)]
|
||||
async fn resolve(
|
||||
@@ -427,7 +401,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
) -> serror::Result<ComposeUpResponse> {
|
||||
let ComposeUp {
|
||||
stack,
|
||||
services,
|
||||
service,
|
||||
git_token,
|
||||
registry_token,
|
||||
replacers,
|
||||
@@ -435,7 +409,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
let mut res = ComposeUpResponse::default();
|
||||
if let Err(e) = compose_up(
|
||||
stack,
|
||||
services,
|
||||
service,
|
||||
git_token,
|
||||
registry_token,
|
||||
&mut res,
|
||||
@@ -444,7 +418,7 @@ impl Resolve<super::Args> for ComposeUp {
|
||||
.await
|
||||
{
|
||||
res.logs.push(Log::error(
|
||||
"Compose Up - Failed",
|
||||
"compose up failed",
|
||||
format_serror(&e.into()),
|
||||
));
|
||||
};
|
||||
@@ -460,9 +434,10 @@ impl Resolve<super::Args> for ComposeExecution {
|
||||
let ComposeExecution { project, command } = self;
|
||||
let docker_compose = docker_compose();
|
||||
let log = run_komodo_command(
|
||||
"Compose Command",
|
||||
"compose command",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} {command}"),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
Ok(log)
|
||||
|
||||
@@ -44,7 +44,10 @@ impl Resolve<super::Args> for GetContainerLog {
|
||||
timestamps.then_some(" --timestamps").unwrap_or_default();
|
||||
let command =
|
||||
format!("docker logs {name} --tail {tail}{timestamps}");
|
||||
Ok(run_komodo_command("Get container log", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("get container log", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +70,13 @@ impl Resolve<super::Args> for GetContainerLogSearch {
|
||||
"docker logs {name} --tail 5000{timestamps} 2>&1 | {grep}"
|
||||
);
|
||||
Ok(
|
||||
run_komodo_command("Get container log grep", None, command)
|
||||
.await,
|
||||
run_komodo_command(
|
||||
"get container log grep",
|
||||
None,
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -109,9 +117,10 @@ impl Resolve<super::Args> for StartContainer {
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
"Docker Start",
|
||||
"docker start",
|
||||
None,
|
||||
format!("docker start {}", self.name),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -125,9 +134,10 @@ impl Resolve<super::Args> for RestartContainer {
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
"Docker Restart",
|
||||
"docker restart",
|
||||
None,
|
||||
format!("docker restart {}", self.name),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -141,9 +151,10 @@ impl Resolve<super::Args> for PauseContainer {
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
"Docker Pause",
|
||||
"docker pause",
|
||||
None,
|
||||
format!("docker pause {}", self.name),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -155,9 +166,10 @@ impl Resolve<super::Args> for UnpauseContainer {
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
Ok(
|
||||
run_komodo_command(
|
||||
"Docker Unpause",
|
||||
"docker unpause",
|
||||
None,
|
||||
format!("docker unpause {}", self.name),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -171,11 +183,12 @@ impl Resolve<super::Args> for StopContainer {
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let StopContainer { name, signal, time } = self;
|
||||
let command = stop_container_command(&name, signal, time);
|
||||
let log = run_komodo_command("Docker Stop", None, command).await;
|
||||
let log =
|
||||
run_komodo_command("docker stop", None, command, false).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let command = stop_container_command(&name, None, time);
|
||||
let mut log =
|
||||
run_komodo_command("Docker Stop", None, command).await;
|
||||
run_komodo_command("docker stop", None, command, false).await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
@@ -200,18 +213,21 @@ impl Resolve<super::Args> for RemoveContainer {
|
||||
let stop_command = stop_container_command(&name, signal, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let log =
|
||||
run_komodo_command("Docker Stop and Remove", None, command)
|
||||
.await;
|
||||
let log = run_komodo_command(
|
||||
"docker stop and remove",
|
||||
None,
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let stop_command = stop_container_command(&name, None, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let mut log =
|
||||
run_komodo_command("Docker Stop and Remove", None, command)
|
||||
.await;
|
||||
run_komodo_command("docker stop", None, command, false).await;
|
||||
log.stderr = format!(
|
||||
"Old docker version: unable to use --signal flag{}",
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
format!("\n\n{}", log.stderr)
|
||||
} else {
|
||||
@@ -236,7 +252,9 @@ impl Resolve<super::Args> for RenameContainer {
|
||||
} = self;
|
||||
let new = to_komodo_name(&new_name);
|
||||
let command = format!("docker rename {curr_name} {new}");
|
||||
Ok(run_komodo_command("Docker Rename", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("docker rename", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +264,10 @@ impl Resolve<super::Args> for PruneContainers {
|
||||
#[instrument(name = "PruneContainers", skip_all)]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = String::from("docker container prune -f");
|
||||
Ok(run_komodo_command("Prune Containers", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune containers", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +290,8 @@ impl Resolve<super::Args> for StartAllContainers {
|
||||
}
|
||||
let command = format!("docker start {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -296,7 +318,8 @@ impl Resolve<super::Args> for RestartAllContainers {
|
||||
}
|
||||
let command = format!("docker restart {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -323,7 +346,8 @@ impl Resolve<super::Args> for PauseAllContainers {
|
||||
}
|
||||
let command = format!("docker pause {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -350,7 +374,8 @@ impl Resolve<super::Args> for UnpauseAllContainers {
|
||||
}
|
||||
let command = format!("docker unpause {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -380,6 +405,7 @@ impl Resolve<super::Args> for StopAllContainers {
|
||||
&format!("docker stop {name}"),
|
||||
None,
|
||||
stop_container_command(name, None, None),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
})
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use command::{
|
||||
run_komodo_command, run_komodo_command_with_interpolation,
|
||||
};
|
||||
use command::run_komodo_command;
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
entities::{
|
||||
@@ -90,22 +88,33 @@ impl Resolve<super::Args> for Deploy {
|
||||
debug!("docker run command: {command}");
|
||||
|
||||
if deployment.config.skip_secret_interp {
|
||||
Ok(run_komodo_command("Docker Run", None, command).await)
|
||||
Ok(run_komodo_command("docker run", None, command, false).await)
|
||||
} else {
|
||||
match run_komodo_command_with_interpolation(
|
||||
"Docker Run",
|
||||
None,
|
||||
command,
|
||||
false,
|
||||
let command = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
&core_replacers,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Some(log) => Ok(log),
|
||||
// The None case can not be reached, as the command is always non-empty
|
||||
None => unreachable!(),
|
||||
}
|
||||
.context(
|
||||
"failed to interpolate secrets into docker run command",
|
||||
);
|
||||
|
||||
let (command, mut replacers) = match command {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
return Ok(Log::error("docker run", format!("{e:?}")));
|
||||
}
|
||||
};
|
||||
|
||||
replacers.extend(core_replacers);
|
||||
let mut log =
|
||||
run_komodo_command("docker run", None, command, false).await;
|
||||
log.command = svi::replace_in_string(&log.command, &replacers);
|
||||
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
|
||||
log.stderr = svi::replace_in_string(&log.stderr, &replacers);
|
||||
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +75,10 @@ impl Resolve<super::Args> for PullImage {
|
||||
.await?;
|
||||
anyhow::Ok(
|
||||
run_komodo_command(
|
||||
"Docker Pull",
|
||||
"docker pull",
|
||||
None,
|
||||
format!("docker pull {name}"),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -98,7 +99,7 @@ impl Resolve<super::Args> for DeleteImage {
|
||||
#[instrument(name = "DeleteImage")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = format!("docker image rm {}", self.name);
|
||||
Ok(run_komodo_command("Delete Image", None, command).await)
|
||||
Ok(run_komodo_command("delete image", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +109,6 @@ impl Resolve<super::Args> for PruneImages {
|
||||
#[instrument(name = "PruneImages")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = String::from("docker image prune -a -f");
|
||||
Ok(run_komodo_command("Prune Images", None, command).await)
|
||||
Ok(run_komodo_command("prune images", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ pub enum PeripheryRequest {
|
||||
|
||||
// Compose (Read)
|
||||
GetComposeContentsOnHost(GetComposeContentsOnHost),
|
||||
GetComposeLog(GetComposeLog),
|
||||
GetComposeLogSearch(GetComposeLogSearch),
|
||||
GetComposeServiceLog(GetComposeServiceLog),
|
||||
GetComposeServiceLogSearch(GetComposeServiceLogSearch),
|
||||
|
||||
// Compose (Write)
|
||||
WriteComposeContentsToHost(WriteComposeContentsToHost),
|
||||
@@ -247,7 +247,7 @@ impl Resolve<Args> for RunCommand {
|
||||
} else {
|
||||
format!("cd {path} && {command}")
|
||||
};
|
||||
run_komodo_command("run command", None, command).await
|
||||
run_komodo_command("run command", None, command, false).await
|
||||
})
|
||||
.await
|
||||
.context("failure in spawned task")?;
|
||||
@@ -259,6 +259,6 @@ impl Resolve<Args> for PruneSystem {
|
||||
#[instrument(name = "PruneSystem", skip_all)]
|
||||
async fn resolve(self, _: &Args) -> serror::Result<Log> {
|
||||
let command = String::from("docker system prune -a -f --volumes");
|
||||
Ok(run_komodo_command("Prune System", None, command).await)
|
||||
Ok(run_komodo_command("prune system", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@ impl Resolve<super::Args> for CreateNetwork {
|
||||
None => String::new(),
|
||||
};
|
||||
let command = format!("docker network create{driver} {name}");
|
||||
Ok(run_komodo_command("Create Network", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("create network", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +40,10 @@ impl Resolve<super::Args> for DeleteNetwork {
|
||||
#[instrument(name = "DeleteNetwork", skip(self))]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = format!("docker network rm {}", self.name);
|
||||
Ok(run_komodo_command("Delete Network", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("delete network", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +53,9 @@ impl Resolve<super::Args> for PruneNetworks {
|
||||
#[instrument(name = "PruneNetworks", skip(self))]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = String::from("docker network prune -f");
|
||||
Ok(run_komodo_command("Prune Networks", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune networks", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ impl Resolve<super::Args> for DeleteVolume {
|
||||
#[instrument(name = "DeleteVolume")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = format!("docker volume rm {}", self.name);
|
||||
Ok(run_komodo_command("Delete Volume", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("delete volume", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +32,8 @@ impl Resolve<super::Args> for PruneVolumes {
|
||||
#[instrument(name = "PruneVolumes")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let command = String::from("docker volume prune -a -f");
|
||||
Ok(run_komodo_command("Prune Volumes", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune volumes", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+227
-194
@@ -1,10 +1,7 @@
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use command::{
|
||||
run_komodo_command, run_komodo_command_multiline,
|
||||
run_komodo_command_with_interpolation,
|
||||
};
|
||||
use command::run_komodo_command;
|
||||
use formatting::format_serror;
|
||||
use git::environment;
|
||||
use komodo_client::entities::{
|
||||
@@ -25,8 +22,9 @@ use resolver_api::Resolve;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config, docker::docker_login,
|
||||
helpers::parse_extra_args,
|
||||
config::periphery_config,
|
||||
docker::docker_login,
|
||||
helpers::{interpolate_variables, parse_extra_args},
|
||||
};
|
||||
|
||||
pub fn docker_compose() -> &'static str {
|
||||
@@ -40,7 +38,7 @@ pub fn docker_compose() -> &'static str {
|
||||
/// If this fn returns Err, the caller of `compose_up` has to write result to the log before return.
|
||||
pub async fn compose_up(
|
||||
stack: Stack,
|
||||
services: Vec<String>,
|
||||
service: Option<String>,
|
||||
git_token: Option<String>,
|
||||
registry_token: Option<String>,
|
||||
res: &mut ComposeUpResponse,
|
||||
@@ -49,21 +47,11 @@ pub async fn compose_up(
|
||||
// Write the stack to local disk. For repos, will first delete any existing folder to ensure fresh deploy.
|
||||
// Will also set additional fields on the reponse.
|
||||
// Use the env_file_path in the compose command.
|
||||
let (run_directory, env_file_path, periphery_replacers) =
|
||||
let (run_directory, env_file_path) =
|
||||
write_stack(&stack, git_token, &mut *res)
|
||||
.await
|
||||
.context("Failed to write / clone compose file")?;
|
||||
|
||||
let replacers =
|
||||
if let Some(periphery_replacers) = periphery_replacers {
|
||||
core_replacers
|
||||
.into_iter()
|
||||
.chain(periphery_replacers)
|
||||
.collect()
|
||||
} else {
|
||||
core_replacers
|
||||
};
|
||||
|
||||
// Canonicalize the path to ensure it exists, and is the cleanest path to the run directory.
|
||||
let run_directory = run_directory.canonicalize().context(
|
||||
"Failed to validate run directory on host after stack write (canonicalize error)",
|
||||
@@ -91,29 +79,28 @@ pub async fn compose_up(
|
||||
}
|
||||
|
||||
for (path, full_path) in &file_paths {
|
||||
let file_contents = match fs::read_to_string(&full_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
let file_contents =
|
||||
match fs::read_to_string(&full_path).await.with_context(|| {
|
||||
format!(
|
||||
"failed to read compose file contents at {full_path:?}"
|
||||
)
|
||||
}) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
let error = format_serror(&e.into());
|
||||
res
|
||||
.logs
|
||||
.push(Log::error("read compose file", error.clone()));
|
||||
// This should only happen for repo stacks, ie remote error
|
||||
res.remote_errors.push(FileContents {
|
||||
path: path.to_string(),
|
||||
contents: error,
|
||||
});
|
||||
return Err(anyhow!(
|
||||
"failed to read compose file at {full_path:?}, stopping run"
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
let error = format_serror(&e.into());
|
||||
res
|
||||
.logs
|
||||
.push(Log::error("read compose file", error.clone()));
|
||||
// This should only happen for repo stacks, ie remote error
|
||||
res.remote_errors.push(FileContents {
|
||||
path: path.to_string(),
|
||||
contents: error,
|
||||
});
|
||||
return Err(anyhow!(
|
||||
"failed to read compose file at {full_path:?}, stopping run"
|
||||
));
|
||||
}
|
||||
};
|
||||
res.file_contents.push(FileContents {
|
||||
path: path.to_string(),
|
||||
contents: file_contents,
|
||||
@@ -121,12 +108,10 @@ pub async fn compose_up(
|
||||
}
|
||||
|
||||
let docker_compose = docker_compose();
|
||||
|
||||
let service_args = if services.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {}", services.join(" "))
|
||||
};
|
||||
let service_arg = service
|
||||
.as_ref()
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
let file_args = if stack.config.file_paths.is_empty() {
|
||||
String::from("compose.yaml")
|
||||
@@ -174,12 +159,13 @@ pub async fn compose_up(
|
||||
// after performing interpolation
|
||||
{
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{additional_env_files}{env_file} config",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{additional_env_files}{env_file} config --format json",
|
||||
);
|
||||
let config_log = run_komodo_command(
|
||||
"Compose Config",
|
||||
"compose build",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
if !config_log.success {
|
||||
@@ -188,11 +174,8 @@ pub async fn compose_up(
|
||||
"Failed to validate compose files, stopping the run."
|
||||
));
|
||||
}
|
||||
// Record sanitized compose config output
|
||||
res.compose_config =
|
||||
Some(svi::replace_in_string(&config_log.stdout, &replacers));
|
||||
let compose =
|
||||
serde_yaml::from_str::<ComposeFile>(&config_log.stdout)
|
||||
serde_json::from_str::<ComposeFile>(&config_log.stdout)
|
||||
.context("Failed to parse compose contents")?;
|
||||
for (
|
||||
service_name,
|
||||
@@ -237,26 +220,38 @@ pub async fn compose_up(
|
||||
let build_extra_args =
|
||||
parse_extra_args(&stack.config.build_extra_args);
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} build{build_extra_args}{service_args}",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} build{build_extra_args}{service_arg}",
|
||||
);
|
||||
if stack.config.skip_secret_interp {
|
||||
let log = run_komodo_command(
|
||||
"Compose Build",
|
||||
"compose build",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
res.logs.push(log);
|
||||
} else if let Some(log) = run_komodo_command_with_interpolation(
|
||||
"Compose Build",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
&periphery_config().secrets,
|
||||
&replacers,
|
||||
)
|
||||
.await
|
||||
{
|
||||
} else {
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
).context("failed to interpolate periphery secrets into stack build command")?;
|
||||
replacers.extend(core_replacers.clone());
|
||||
|
||||
let mut log = run_komodo_command(
|
||||
"compose build",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
log.command = svi::replace_in_string(&log.command, &replacers);
|
||||
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
|
||||
log.stderr = svi::replace_in_string(&log.stderr, &replacers);
|
||||
|
||||
res.logs.push(log);
|
||||
}
|
||||
|
||||
@@ -272,11 +267,12 @@ pub async fn compose_up(
|
||||
// Pull images before destroying to minimize downtime.
|
||||
// If this fails, do not continue.
|
||||
let log = run_komodo_command(
|
||||
"Compose Pull",
|
||||
"compose pull",
|
||||
run_directory.as_ref(),
|
||||
format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} pull{service_args}",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} pull{service_arg}",
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -289,33 +285,58 @@ pub async fn compose_up(
|
||||
}
|
||||
}
|
||||
|
||||
// Pre deploy command
|
||||
let pre_deploy_path =
|
||||
run_directory.join(&stack.config.pre_deploy.path);
|
||||
if let Some(log) = if stack.config.skip_secret_interp {
|
||||
run_komodo_command_multiline(
|
||||
"Pre Deploy",
|
||||
pre_deploy_path.as_ref(),
|
||||
&stack.config.pre_deploy.command,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
run_komodo_command_with_interpolation(
|
||||
"Pre Deploy",
|
||||
pre_deploy_path.as_ref(),
|
||||
&stack.config.pre_deploy.command,
|
||||
true,
|
||||
&periphery_config().secrets,
|
||||
&replacers,
|
||||
)
|
||||
.await
|
||||
} {
|
||||
res.logs.push(log);
|
||||
}
|
||||
if !all_logs_success(&res.logs) {
|
||||
return Err(anyhow!(
|
||||
"Failed at running pre_deploy command, stopping the run."
|
||||
));
|
||||
if !stack.config.pre_deploy.command.is_empty() {
|
||||
let pre_deploy_path =
|
||||
run_directory.join(&stack.config.pre_deploy.path);
|
||||
if !stack.config.skip_secret_interp {
|
||||
let (full_command, mut replacers) =
|
||||
interpolate_variables(&stack.config.pre_deploy.command)
|
||||
.context(
|
||||
"failed to interpolate secrets into pre_deploy command",
|
||||
)?;
|
||||
replacers.extend(core_replacers.to_owned());
|
||||
let mut pre_deploy_log = run_komodo_command(
|
||||
"pre deploy",
|
||||
pre_deploy_path.as_ref(),
|
||||
&full_command,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
pre_deploy_log.command =
|
||||
svi::replace_in_string(&pre_deploy_log.command, &replacers);
|
||||
pre_deploy_log.stdout =
|
||||
svi::replace_in_string(&pre_deploy_log.stdout, &replacers);
|
||||
pre_deploy_log.stderr =
|
||||
svi::replace_in_string(&pre_deploy_log.stderr, &replacers);
|
||||
|
||||
tracing::debug!(
|
||||
"run Stack pre_deploy command | command: {} | cwd: {:?}",
|
||||
pre_deploy_log.command,
|
||||
pre_deploy_path
|
||||
);
|
||||
|
||||
res.logs.push(pre_deploy_log);
|
||||
} else {
|
||||
let pre_deploy_log = run_komodo_command(
|
||||
"pre deploy",
|
||||
pre_deploy_path.as_ref(),
|
||||
&stack.config.pre_deploy.command,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
tracing::debug!(
|
||||
"run Stack pre_deploy command | command: {} | cwd: {:?}",
|
||||
&stack.config.pre_deploy.command,
|
||||
pre_deploy_path
|
||||
);
|
||||
res.logs.push(pre_deploy_log);
|
||||
}
|
||||
if !all_logs_success(&res.logs) {
|
||||
return Err(anyhow!(
|
||||
"Failed at running pre_deploy command, stopping the run."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if stack.config.destroy_before_deploy
|
||||
@@ -324,7 +345,7 @@ pub async fn compose_up(
|
||||
{
|
||||
// Take down the existing containers.
|
||||
// This one tries to use the previously deployed service name, to ensure the right stack is taken down.
|
||||
compose_down(&last_project_name, &services, res)
|
||||
compose_down(&last_project_name, service, res)
|
||||
.await
|
||||
.context("failed to destroy existing containers")?;
|
||||
}
|
||||
@@ -332,27 +353,39 @@ pub async fn compose_up(
|
||||
// Run compose up
|
||||
let extra_args = parse_extra_args(&stack.config.extra_args);
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} up -d{extra_args}{service_args}",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} up -d{extra_args}{service_arg}",
|
||||
);
|
||||
|
||||
let log = if stack.config.skip_secret_interp {
|
||||
run_komodo_command("Compose Up", run_directory.as_ref(), command)
|
||||
.await
|
||||
} else {
|
||||
match run_komodo_command_with_interpolation(
|
||||
"Compose Up",
|
||||
run_komodo_command(
|
||||
"compose up",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
&periphery_config().secrets,
|
||||
&replacers,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Some(log) => log,
|
||||
// The command is definitely non-empty, the result will never be None.
|
||||
None => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
).context("failed to interpolate periphery secrets into stack run command")?;
|
||||
replacers.extend(core_replacers.clone());
|
||||
|
||||
let mut log = run_komodo_command(
|
||||
"compose up",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
log.command = svi::replace_in_string(&log.command, &replacers);
|
||||
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
|
||||
log.stderr = svi::replace_in_string(&log.stderr, &replacers);
|
||||
|
||||
log
|
||||
};
|
||||
|
||||
res.deployed = log.success;
|
||||
@@ -360,28 +393,52 @@ pub async fn compose_up(
|
||||
// push the compose up command logs to keep the correct order
|
||||
res.logs.push(log);
|
||||
|
||||
if res.deployed {
|
||||
if res.deployed && !stack.config.post_deploy.command.is_empty() {
|
||||
let post_deploy_path =
|
||||
run_directory.join(&stack.config.post_deploy.path);
|
||||
if let Some(log) = if stack.config.skip_secret_interp {
|
||||
run_komodo_command_multiline(
|
||||
"Post Deploy",
|
||||
run_directory.join(&stack.config.post_deploy.path);
|
||||
if !stack.config.skip_secret_interp {
|
||||
let (full_command, mut replacers) =
|
||||
interpolate_variables(&stack.config.post_deploy.command)
|
||||
.context(
|
||||
"failed to interpolate secrets into post_deploy command",
|
||||
)?;
|
||||
replacers.extend(core_replacers);
|
||||
let mut post_deploy_log = run_komodo_command(
|
||||
"post deploy",
|
||||
post_deploy_path.as_ref(),
|
||||
&stack.config.post_deploy.command,
|
||||
&full_command,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.await;
|
||||
|
||||
post_deploy_log.command =
|
||||
svi::replace_in_string(&post_deploy_log.command, &replacers);
|
||||
post_deploy_log.stdout =
|
||||
svi::replace_in_string(&post_deploy_log.stdout, &replacers);
|
||||
post_deploy_log.stderr =
|
||||
svi::replace_in_string(&post_deploy_log.stderr, &replacers);
|
||||
|
||||
tracing::debug!(
|
||||
"run Stack post_deploy command | command: {} | cwd: {:?}",
|
||||
post_deploy_log.command,
|
||||
post_deploy_path
|
||||
);
|
||||
|
||||
res.logs.push(post_deploy_log);
|
||||
} else {
|
||||
run_komodo_command_with_interpolation(
|
||||
"Post Deploy",
|
||||
let post_deploy_log = run_komodo_command(
|
||||
"post deploy",
|
||||
post_deploy_path.as_ref(),
|
||||
&stack.config.post_deploy.command,
|
||||
true,
|
||||
&periphery_config().secrets,
|
||||
&replacers,
|
||||
)
|
||||
.await
|
||||
} {
|
||||
res.logs.push(log)
|
||||
.await;
|
||||
tracing::debug!(
|
||||
"run Stack post_deploy command | command: {} | cwd: {:?}",
|
||||
&stack.config.post_deploy.command,
|
||||
post_deploy_path
|
||||
);
|
||||
res.logs.push(post_deploy_log);
|
||||
}
|
||||
if !all_logs_success(&res.logs) {
|
||||
return Err(anyhow!(
|
||||
@@ -400,7 +457,7 @@ pub trait WriteStackRes {
|
||||
fn set_commit_message(&mut self, _message: Option<String>) {}
|
||||
}
|
||||
|
||||
impl WriteStackRes for &mut ComposeUpResponse {
|
||||
impl<'a> WriteStackRes for &'a mut ComposeUpResponse {
|
||||
fn logs(&mut self) -> &mut Vec<Log> {
|
||||
&mut self.logs
|
||||
}
|
||||
@@ -416,17 +473,12 @@ impl WriteStackRes for &mut ComposeUpResponse {
|
||||
}
|
||||
|
||||
/// Either writes the stack file_contents to a file, or clones the repo.
|
||||
/// Performs variable replacement on env and writes file.
|
||||
/// Returns (run_directory, env_file_path, periphery_replacers)
|
||||
/// Returns (run_directory, env_file_path)
|
||||
pub async fn write_stack(
|
||||
stack: &Stack,
|
||||
git_token: Option<String>,
|
||||
mut res: impl WriteStackRes,
|
||||
) -> anyhow::Result<(
|
||||
PathBuf,
|
||||
Option<&str>,
|
||||
Option<Vec<(String, String)>>,
|
||||
)> {
|
||||
) -> anyhow::Result<(PathBuf, Option<&str>)> {
|
||||
let root = periphery_config()
|
||||
.stack_dir
|
||||
.join(to_komodo_name(&stack.name));
|
||||
@@ -435,61 +487,42 @@ pub async fn write_stack(
|
||||
// Cannot use 'canonicalize' yet as directory may not exist.
|
||||
let run_directory = run_directory.components().collect::<PathBuf>();
|
||||
|
||||
let (env_interpolated, env_replacers) =
|
||||
if stack.config.skip_secret_interp {
|
||||
(stack.config.environment.clone(), None)
|
||||
} else {
|
||||
let (environment, replacers) = svi::interpolate_variables(
|
||||
&stack.config.environment,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context(
|
||||
"Failed to interpolate Periphery secrets into Environment",
|
||||
)?;
|
||||
(environment, Some(replacers))
|
||||
};
|
||||
match &env_replacers {
|
||||
Some(replacers) if !replacers.is_empty() => {
|
||||
res.logs().push(Log::simple(
|
||||
"Interpolate - Environment (Periphery)",
|
||||
replacers
|
||||
.iter()
|
||||
.map(|(_, variable)| format!("<span class=\"text-muted-foreground\">replaced:</span> {variable}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let env_vars = environment_vars_from_str(&env_interpolated)
|
||||
let env_vars = environment_vars_from_str(&stack.config.environment)
|
||||
.context("Invalid environment variables")?;
|
||||
|
||||
if stack.config.files_on_host {
|
||||
// =============
|
||||
// FILES ON HOST
|
||||
// =============
|
||||
let env_file_path = environment::write_file_simple(
|
||||
// Only need to write environment file here (which does nothing if not using this feature)
|
||||
let env_file_path = match environment::write_file(
|
||||
&env_vars,
|
||||
&stack.config.env_file_path,
|
||||
stack
|
||||
.config
|
||||
.skip_secret_interp
|
||||
.then_some(&periphery_config().secrets),
|
||||
run_directory.as_ref(),
|
||||
res.logs(),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
return Err(anyhow!("failed to write environment file"));
|
||||
}
|
||||
};
|
||||
Ok((
|
||||
run_directory,
|
||||
// Env file paths are expected to be already relative to run directory,
|
||||
// Env file paths are already relative to run directory,
|
||||
// so need to pass original env_file_path here.
|
||||
env_file_path
|
||||
.is_some()
|
||||
.then_some(&stack.config.env_file_path),
|
||||
env_replacers,
|
||||
))
|
||||
} else if stack.config.repo.is_empty() {
|
||||
if stack.config.file_contents.trim().is_empty() {
|
||||
return Err(anyhow!("Must either input compose file contents directly, or use files on host / git repo options."));
|
||||
return Err(anyhow!("Must either input compose file contents directly, or use file one host / git repo options."));
|
||||
}
|
||||
// ==============
|
||||
// UI BASED FILES
|
||||
@@ -500,13 +533,23 @@ pub async fn write_stack(
|
||||
"failed to create stack run directory at {run_directory:?}"
|
||||
)
|
||||
})?;
|
||||
let env_file_path = environment::write_file_simple(
|
||||
let env_file_path = match environment::write_file(
|
||||
&env_vars,
|
||||
&stack.config.env_file_path,
|
||||
stack
|
||||
.config
|
||||
.skip_secret_interp
|
||||
.then_some(&periphery_config().secrets),
|
||||
run_directory.as_ref(),
|
||||
res.logs(),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
return Err(anyhow!("failed to write environment file"));
|
||||
}
|
||||
};
|
||||
let file_path = run_directory
|
||||
.join(
|
||||
stack
|
||||
@@ -520,10 +563,7 @@ pub async fn write_stack(
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
|
||||
let (file_contents, file_replacers) = if !stack
|
||||
.config
|
||||
.skip_secret_interp
|
||||
{
|
||||
let file_contents = if !stack.config.skip_secret_interp {
|
||||
let (contents, replacers) = svi::interpolate_variables(
|
||||
&stack.config.file_contents,
|
||||
&periphery_config().secrets,
|
||||
@@ -533,7 +573,7 @@ pub async fn write_stack(
|
||||
.context("failed to interpolate secrets into file contents")?;
|
||||
if !replacers.is_empty() {
|
||||
res.logs().push(Log::simple(
|
||||
"Interpolate - Compose file (Periphery)",
|
||||
"Interpolate - Compose file",
|
||||
replacers
|
||||
.iter()
|
||||
.map(|(_, variable)| format!("<span class=\"text-muted-foreground\">replaced:</span> {variable}"))
|
||||
@@ -541,13 +581,13 @@ pub async fn write_stack(
|
||||
.join("\n"),
|
||||
));
|
||||
}
|
||||
(contents, Some(replacers))
|
||||
contents
|
||||
} else {
|
||||
(stack.config.file_contents.clone(), None)
|
||||
stack.config.file_contents.clone()
|
||||
};
|
||||
|
||||
fs::write(&file_path, &file_contents).await.with_context(
|
||||
|| format!("Failed to write compose file to {file_path:?}"),
|
||||
|| format!("failed to write compose file to {file_path:?}"),
|
||||
)?;
|
||||
|
||||
Ok((
|
||||
@@ -555,14 +595,6 @@ pub async fn write_stack(
|
||||
env_file_path
|
||||
.is_some()
|
||||
.then_some(&stack.config.env_file_path),
|
||||
match (env_replacers, file_replacers) {
|
||||
(Some(env_replacers), Some(file_replacers)) => Some(
|
||||
env_replacers.into_iter().chain(file_replacers).collect(),
|
||||
),
|
||||
(Some(env_replacers), None) => Some(env_replacers),
|
||||
(None, Some(file_replacers)) => Some(file_replacers),
|
||||
(None, None) => None,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
// ================
|
||||
@@ -616,8 +648,9 @@ pub async fn write_stack(
|
||||
git_token,
|
||||
environment: env_vars,
|
||||
env_file_path,
|
||||
// Env has already been interpolated above
|
||||
skip_secret_interp: true,
|
||||
skip_secret_interp: stack.config.skip_secret_interp,
|
||||
// repo replacer only needed for on_clone / on_pull,
|
||||
// which aren't available for stacks
|
||||
replacers: Default::default(),
|
||||
}
|
||||
.resolve(&crate::api::Args)
|
||||
@@ -628,8 +661,9 @@ pub async fn write_stack(
|
||||
git_token,
|
||||
environment: env_vars,
|
||||
env_file_path,
|
||||
// Env has already been interpolated above
|
||||
skip_secret_interp: true,
|
||||
skip_secret_interp: stack.config.skip_secret_interp,
|
||||
// repo replacer only needed for on_clone / on_pull,
|
||||
// which aren't available for stacks
|
||||
replacers: Default::default(),
|
||||
}
|
||||
.resolve(&crate::api::Args)
|
||||
@@ -645,17 +679,17 @@ pub async fn write_stack(
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
let error = format_serror(
|
||||
&e.error.context("Failed to pull stack repo").into(),
|
||||
&e.error.context("failed to pull stack repo").into(),
|
||||
);
|
||||
res
|
||||
.logs()
|
||||
.push(Log::error("Pull Stack Repo", error.clone()));
|
||||
.push(Log::error("pull stack repo", error.clone()));
|
||||
res.add_remote_error(FileContents {
|
||||
path: Default::default(),
|
||||
contents: error,
|
||||
});
|
||||
return Err(anyhow!(
|
||||
"Failed to pull stack repo, stopping run"
|
||||
"failed to pull stack repo, stopping run"
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -673,26 +707,25 @@ pub async fn write_stack(
|
||||
env_file_path
|
||||
.is_some()
|
||||
.then_some(&stack.config.env_file_path),
|
||||
env_replacers,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn compose_down(
|
||||
project: &str,
|
||||
services: &[String],
|
||||
service: Option<String>,
|
||||
res: &mut ComposeUpResponse,
|
||||
) -> anyhow::Result<()> {
|
||||
let docker_compose = docker_compose();
|
||||
let service_args = if services.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {}", services.join(" "))
|
||||
};
|
||||
let service_arg = service
|
||||
.as_ref()
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
let log = run_komodo_command(
|
||||
"compose down",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} down{service_args}"),
|
||||
format!("{docker_compose} -p {project} down{service_arg}"),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
let success = log.success;
|
||||
|
||||
@@ -969,7 +969,7 @@ pub async fn docker_login(
|
||||
#[instrument]
|
||||
pub async fn pull_image(image: &str) -> Log {
|
||||
let command = format!("docker pull {image}");
|
||||
run_komodo_command("Docker Pull", None, command).await
|
||||
run_komodo_command("docker pull", None, command, false).await
|
||||
}
|
||||
|
||||
pub fn stop_container_command(
|
||||
|
||||
@@ -86,6 +86,17 @@ pub fn log_grep(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interpolate_variables(
|
||||
input: &str,
|
||||
) -> svi::Result<(String, Vec<(String, String)>)> {
|
||||
svi::interpolate_variables(
|
||||
input,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns path to root directory of the stack repo.
|
||||
pub async fn pull_or_clone_stack(
|
||||
stack: &Stack,
|
||||
|
||||
@@ -26,10 +26,8 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest};
|
||||
pub struct DeployStack {
|
||||
/// Id or name
|
||||
pub stack: String,
|
||||
/// Filter to only deploy specific services.
|
||||
/// If empty, will deploy all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to "compose up"
|
||||
pub service: Option<String>,
|
||||
/// Override the default termination max time.
|
||||
/// Only used if the stack needs to be taken down first.
|
||||
pub stop_time: Option<i32>,
|
||||
@@ -144,10 +142,8 @@ pub struct BatchDeployStackIfChanged {
|
||||
pub struct PullStack {
|
||||
/// Id or name
|
||||
pub stack: String,
|
||||
/// Filter to only pull specific services.
|
||||
/// If empty, will pull all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to start
|
||||
pub service: Option<String>,
|
||||
}
|
||||
|
||||
//
|
||||
@@ -170,10 +166,8 @@ pub struct PullStack {
|
||||
pub struct StartStack {
|
||||
/// Id or name
|
||||
pub stack: String,
|
||||
/// Filter to only start specific services.
|
||||
/// If empty, will start all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to start
|
||||
pub service: Option<String>,
|
||||
}
|
||||
|
||||
//
|
||||
@@ -196,10 +190,8 @@ pub struct StartStack {
|
||||
pub struct RestartStack {
|
||||
/// Id or name
|
||||
pub stack: String,
|
||||
/// Filter to only restart specific services.
|
||||
/// If empty, will restart all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to restart
|
||||
pub service: Option<String>,
|
||||
}
|
||||
|
||||
//
|
||||
@@ -222,10 +214,8 @@ pub struct RestartStack {
|
||||
pub struct PauseStack {
|
||||
/// Id or name
|
||||
pub stack: String,
|
||||
/// Filter to only pause specific services.
|
||||
/// If empty, will pause all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to pause
|
||||
pub service: Option<String>,
|
||||
}
|
||||
|
||||
//
|
||||
@@ -250,10 +240,8 @@ pub struct PauseStack {
|
||||
pub struct UnpauseStack {
|
||||
/// Id or name
|
||||
pub stack: String,
|
||||
/// Filter to only unpause specific services.
|
||||
/// If empty, will unpause all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to unpause
|
||||
pub service: Option<String>,
|
||||
}
|
||||
|
||||
//
|
||||
@@ -278,10 +266,8 @@ pub struct StopStack {
|
||||
pub stack: String,
|
||||
/// Override the default termination max time.
|
||||
pub stop_time: Option<i32>,
|
||||
/// Filter to only stop specific services.
|
||||
/// If empty, will stop all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to stop
|
||||
pub service: Option<String>,
|
||||
}
|
||||
|
||||
//
|
||||
@@ -304,10 +290,8 @@ pub struct StopStack {
|
||||
pub struct DestroyStack {
|
||||
/// Id or name
|
||||
pub stack: String,
|
||||
/// Filter to only destroy specific services.
|
||||
/// If empty, will destroy all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Optionally specify a specific service to destroy
|
||||
pub service: Option<String>,
|
||||
/// Pass `--remove-orphans`
|
||||
#[serde(default)]
|
||||
pub remove_orphans: bool,
|
||||
|
||||
@@ -53,23 +53,20 @@ pub type ListStackServicesResponse = Vec<StackService>;
|
||||
|
||||
//
|
||||
|
||||
/// Get a stack's logs. Filter down included services. Response: [GetStackLogResponse].
|
||||
///
|
||||
/// Note. This call will hit the underlying server directly for most up to date log.
|
||||
/// Get a stack service's log. Response: [GetStackServiceLogResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(GetStackLogResponse)]
|
||||
#[response(GetStackServiceLogResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct GetStackLog {
|
||||
pub struct GetStackServiceLog {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub stack: String,
|
||||
/// Filter the logs to only ones from specific services.
|
||||
/// If empty, will include logs from all services.
|
||||
pub services: Vec<String>,
|
||||
/// The service to get the log for.
|
||||
pub service: String,
|
||||
/// The number of lines of the log tail to include.
|
||||
/// Default: 100.
|
||||
/// Max: 5000.
|
||||
@@ -85,12 +82,12 @@ fn default_tail() -> u64 {
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type GetStackLogResponse = Log;
|
||||
pub type GetStackServiceLogResponse = Log;
|
||||
|
||||
//
|
||||
|
||||
/// Search the stack log's tail using `grep`. All lines go to stdout.
|
||||
/// Response: [SearchStackLogResponse].
|
||||
/// Search the deployment log's tail using `grep`. All lines go to stdout.
|
||||
/// Response: [Log].
|
||||
///
|
||||
/// Note. This call will hit the underlying server directly for most up to date log.
|
||||
#[typeshare]
|
||||
@@ -98,15 +95,14 @@ pub type GetStackLogResponse = Log;
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(SearchStackLogResponse)]
|
||||
#[response(SearchStackServiceLogResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct SearchStackLog {
|
||||
pub struct SearchStackServiceLog {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub stack: String,
|
||||
/// Filter the logs to only ones from specific services.
|
||||
/// If empty, will include logs from all services.
|
||||
pub services: Vec<String>,
|
||||
/// The service to get the log for.
|
||||
pub service: String,
|
||||
/// The terms to search for.
|
||||
pub terms: Vec<String>,
|
||||
/// When searching for multiple terms, can use `AND` or `OR` combinator.
|
||||
@@ -124,7 +120,7 @@ pub struct SearchStackLog {
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type SearchStackLogResponse = Log;
|
||||
pub type SearchStackServiceLogResponse = Log;
|
||||
|
||||
//
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ where
|
||||
|
||||
struct FileContentsVisitor;
|
||||
|
||||
impl Visitor<'_> for FileContentsVisitor {
|
||||
impl<'de> Visitor<'de> for FileContentsVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
@@ -47,7 +47,7 @@ impl Visitor<'_> for FileContentsVisitor {
|
||||
|
||||
struct OptionFileContentsVisitor;
|
||||
|
||||
impl Visitor<'_> for OptionFileContentsVisitor {
|
||||
impl<'de> Visitor<'de> for OptionFileContentsVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
|
||||
@@ -20,7 +20,7 @@ where
|
||||
|
||||
struct MaybeStringI64Visitor;
|
||||
|
||||
impl Visitor<'_> for MaybeStringI64Visitor {
|
||||
impl<'de> Visitor<'de> for MaybeStringI64Visitor {
|
||||
type Value = i64;
|
||||
|
||||
fn expecting(
|
||||
@@ -110,7 +110,7 @@ impl Visitor<'_> for MaybeStringI64Visitor {
|
||||
|
||||
struct OptionMaybeStringI64Visitor;
|
||||
|
||||
impl Visitor<'_> for OptionMaybeStringI64Visitor {
|
||||
impl<'de> Visitor<'de> for OptionMaybeStringI64Visitor {
|
||||
type Value = Option<i64>;
|
||||
|
||||
fn expecting(
|
||||
|
||||
@@ -72,7 +72,6 @@ impl AlerterConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for AlerterConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -96,8 +96,6 @@ pub struct Env {
|
||||
pub komodo_enable_new_users: Option<bool>,
|
||||
/// Override `disable_user_registration`
|
||||
pub komodo_disable_user_registration: Option<bool>,
|
||||
/// Override `lock_login_credentials_for`
|
||||
pub komodo_lock_login_credentials_for: Option<Vec<String>>,
|
||||
/// Override `disable_confirm_dialog`
|
||||
pub komodo_disable_confirm_dialog: Option<bool>,
|
||||
/// Override `disable_non_admin_create`
|
||||
@@ -302,13 +300,6 @@ pub struct CoreConfig {
|
||||
#[serde(default)]
|
||||
pub disable_user_registration: bool,
|
||||
|
||||
/// List of usernames for which the update username / password
|
||||
/// APIs are disabled. Used by demo to lock the 'demo' : 'demo' login.
|
||||
///
|
||||
/// To lock the api for all users, use `lock_login_credentials_for = ["__ALL__"]`
|
||||
#[serde(default)]
|
||||
pub lock_login_credentials_for: Vec<String>,
|
||||
|
||||
/// Normally all users can create resources.
|
||||
/// If `disable_non_admin_create = true`, only admins will be able to create resources.
|
||||
#[serde(default)]
|
||||
@@ -590,7 +581,6 @@ impl CoreConfig {
|
||||
enable_new_users: config.enable_new_users,
|
||||
disable_user_registration: config.disable_user_registration,
|
||||
disable_non_admin_create: config.disable_non_admin_create,
|
||||
lock_login_credentials_for: config.lock_login_credentials_for,
|
||||
local_auth: config.local_auth,
|
||||
oidc_enabled: config.oidc_enabled,
|
||||
oidc_provider: config.oidc_provider,
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct GitProvider {
|
||||
/// Whether to use https. Default: true.
|
||||
#[serde(default = "default_git_https")]
|
||||
pub https: bool,
|
||||
/// The accounts on the git provider. Required.
|
||||
/// The account username. Required.
|
||||
#[serde(alias = "account")]
|
||||
pub accounts: Vec<ProviderAccount>,
|
||||
}
|
||||
@@ -52,8 +52,8 @@ pub struct DockerRegistry {
|
||||
/// The docker provider domain. Default: `docker.io`.
|
||||
#[serde(default = "default_docker_provider")]
|
||||
pub domain: String,
|
||||
/// The accounts on the registry. Required.
|
||||
#[serde(alias = "account")]
|
||||
/// The account username. Required.
|
||||
#[serde(default, alias = "account")]
|
||||
pub accounts: Vec<ProviderAccount>,
|
||||
/// Available organizations on the registry provider.
|
||||
/// Used to push an image under an organization's repo rather than an account's repo.
|
||||
|
||||
@@ -163,15 +163,12 @@ pub struct StackInfo {
|
||||
pub deployed_hash: Option<String>,
|
||||
/// Deployed commit message, or null. Only for repo based stacks
|
||||
pub deployed_message: Option<String>,
|
||||
/// The deployed compose file contents.
|
||||
/// This is updated whenever Komodo successfully deploys the stack.
|
||||
/// The deployed compose file contents. This is updated whenever Komodo successfully deploys the stack.
|
||||
pub deployed_contents: Option<Vec<FileContents>>,
|
||||
/// The deployed service names.
|
||||
/// This is updated whenever it is empty, or deployed contents is updated.
|
||||
pub deployed_services: Option<Vec<StackServiceNames>>,
|
||||
/// The output of `docker compose config`.
|
||||
/// This is updated whenever Komodo successfully deploys the stack.
|
||||
pub deployed_config: Option<String>,
|
||||
|
||||
/// The latest service names.
|
||||
/// This is updated whenever the stack cache refreshes, using the latest file contents (either db defined or remote).
|
||||
#[serde(default)]
|
||||
@@ -250,14 +247,6 @@ pub struct StackConfig {
|
||||
#[builder(default)]
|
||||
pub auto_update: bool,
|
||||
|
||||
/// If auto update is enabled, Komodo will
|
||||
/// by default only update the specific services
|
||||
/// with image updates. If this parameter is set to true,
|
||||
/// Komodo will redeploy the whole Stack (all services).
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub auto_update_all_services: bool,
|
||||
|
||||
/// Whether to run `docker compose down` before `compose up`.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
@@ -503,7 +492,6 @@ impl Default for StackConfig {
|
||||
auto_pull: default_auto_pull(),
|
||||
poll_for_updates: Default::default(),
|
||||
auto_update: Default::default(),
|
||||
auto_update_all_services: Default::default(),
|
||||
ignore_services: Default::default(),
|
||||
pre_deploy: Default::default(),
|
||||
post_deploy: Default::default(),
|
||||
|
||||
@@ -229,12 +229,6 @@ pub struct ResourceSyncConfig {
|
||||
#[builder(default)]
|
||||
pub resource_path: Vec<String>,
|
||||
|
||||
/// Excluse Komodo Resources (Servers / Stacks / Builds)
|
||||
/// from the sync. Will be variable / user group only sync.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub exclude_resources: bool,
|
||||
|
||||
/// Enable "pushes" to the file,
|
||||
/// which exports resources matching tags to single file.
|
||||
/// - If using `files_on_host`, it is stored in the file_contents, which must point to a .toml file path (it will be created if it doesn't exist).
|
||||
@@ -315,7 +309,6 @@ impl Default for ResourceSyncConfig {
|
||||
resource_path: Default::default(),
|
||||
files_on_host: Default::default(),
|
||||
file_contents: Default::default(),
|
||||
exclude_resources: Default::default(),
|
||||
managed: Default::default(),
|
||||
match_tags: Default::default(),
|
||||
delete: Default::default(),
|
||||
|
||||
@@ -9,10 +9,8 @@ import {
|
||||
AuthRequest,
|
||||
ExecuteRequest,
|
||||
ReadRequest,
|
||||
UpdateListItem,
|
||||
UserRequest,
|
||||
WriteRequest,
|
||||
WsLoginMessage,
|
||||
} from "./types.js";
|
||||
|
||||
export * as Types from "./types.js";
|
||||
@@ -21,16 +19,6 @@ type InitOptions =
|
||||
| { type: "jwt"; params: { jwt: string } }
|
||||
| { type: "api-key"; params: { key: string; secret: string } };
|
||||
|
||||
export class CancelToken {
|
||||
cancelled: boolean;
|
||||
constructor() {
|
||||
this.cancelled = false;
|
||||
}
|
||||
cancel() {
|
||||
this.cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize a new client for Komodo */
|
||||
export function KomodoClient(url: string, options: InitOptions) {
|
||||
const state = {
|
||||
@@ -119,6 +107,7 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
UserResponses[Req["type"]]
|
||||
>("/user", { type, params });
|
||||
|
||||
|
||||
const read = async <
|
||||
T extends ReadRequest["type"],
|
||||
Req extends Extract<ReadRequest, { type: T }>
|
||||
@@ -131,6 +120,7 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
ReadResponses[Req["type"]]
|
||||
>("/read", { type, params });
|
||||
|
||||
|
||||
const write = async <
|
||||
T extends WriteRequest["type"],
|
||||
Req extends Extract<WriteRequest, { type: T }>
|
||||
@@ -143,6 +133,7 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
WriteResponses[Req["type"]]
|
||||
>("/write", { type, params });
|
||||
|
||||
|
||||
const execute = async <
|
||||
T extends ExecuteRequest["type"],
|
||||
Req extends Extract<ExecuteRequest, { type: T }>
|
||||
@@ -157,77 +148,6 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
|
||||
const core_version = () => read("GetVersion", {}).then((res) => res.version);
|
||||
|
||||
const subscribe_to_update_websocket = async ({
|
||||
on_update,
|
||||
on_login,
|
||||
on_close,
|
||||
retry_timeout_ms = 5_000,
|
||||
cancel = new CancelToken(),
|
||||
on_cancel,
|
||||
}: {
|
||||
on_update: (update: UpdateListItem) => void;
|
||||
on_login?: () => void;
|
||||
on_open?: () => void;
|
||||
on_close?: () => void;
|
||||
retry_timeout_ms?: number;
|
||||
cancel?: CancelToken;
|
||||
on_cancel?: () => void;
|
||||
}) => {
|
||||
while (true) {
|
||||
if (cancel.cancelled) {
|
||||
on_cancel?.();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ws = new WebSocket(url.replace("http", "ws") + "/ws/update");
|
||||
|
||||
// Handle login on websocket open
|
||||
ws.addEventListener("open", () => {
|
||||
const login_msg: WsLoginMessage =
|
||||
options.type === "jwt"
|
||||
? {
|
||||
type: "Jwt",
|
||||
params: {
|
||||
jwt: options.params.jwt,
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: "ApiKeys",
|
||||
params: {
|
||||
key: options.params.key,
|
||||
secret: options.params.secret,
|
||||
},
|
||||
};
|
||||
ws.send(JSON.stringify(login_msg));
|
||||
});
|
||||
|
||||
ws.addEventListener("message", ({ data }: MessageEvent) => {
|
||||
if (data == "LOGGED_IN") return on_login?.();
|
||||
on_update(JSON.parse(data));
|
||||
});
|
||||
|
||||
if (on_close) {
|
||||
ws.addEventListener("close", on_close);
|
||||
}
|
||||
|
||||
// This while loop will end when the socket is closed
|
||||
while (
|
||||
ws.readyState !== WebSocket.CLOSING &&
|
||||
ws.readyState !== WebSocket.CLOSED
|
||||
) {
|
||||
if (cancel.cancelled) ws.close();
|
||||
// Sleep for a bit before checking for websocket closed
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// Sleep for a bit before retrying, maybe Komodo Core is down temporarily.
|
||||
await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Call the `/auth` api.
|
||||
@@ -292,11 +212,5 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
execute,
|
||||
/** Returns the version of Komodo Core the client is calling to. */
|
||||
core_version,
|
||||
/**
|
||||
* Subscribes to the update websocket with automatic reconnect loop.
|
||||
*
|
||||
* Note. Awaiting this method will never finish.
|
||||
*/
|
||||
subscribe_to_update_websocket,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,8 +124,8 @@ export type ReadResponses = {
|
||||
GetStack: Types.GetStackResponse;
|
||||
GetStackActionState: Types.GetStackActionStateResponse;
|
||||
GetStackWebhooksEnabled: Types.GetStackWebhooksEnabledResponse;
|
||||
GetStackLog: Types.GetStackLogResponse;
|
||||
SearchStackLog: Types.SearchStackLogResponse;
|
||||
GetStackServiceLog: Types.GetStackServiceLogResponse;
|
||||
SearchStackServiceLog: Types.SearchStackServiceLogResponse;
|
||||
ListStacks: Types.ListStacksResponse;
|
||||
ListFullStacks: Types.ListFullStacksResponse;
|
||||
ListStackServices: Types.ListStackServicesResponse;
|
||||
|
||||
+168
-222
File diff suppressed because it is too large
Load Diff
@@ -44,13 +44,11 @@ pub struct GetComposeContentsOnHostResponse {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
|
||||
#[response(Log)]
|
||||
#[error(serror::Error)]
|
||||
pub struct GetComposeLog {
|
||||
pub struct GetComposeServiceLog {
|
||||
/// The name of the project
|
||||
pub project: String,
|
||||
/// Filter the logs to only ones from specific services.
|
||||
/// If empty, will include logs from all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// The service name
|
||||
pub service: String,
|
||||
/// Pass `--tail` for only recent log contents. Max of 5000
|
||||
#[serde(default = "default_tail")]
|
||||
pub tail: u64,
|
||||
@@ -69,13 +67,11 @@ fn default_tail() -> u64 {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
|
||||
#[response(Log)]
|
||||
#[error(serror::Error)]
|
||||
pub struct GetComposeLogSearch {
|
||||
pub struct GetComposeServiceLogSearch {
|
||||
/// The name of the project
|
||||
pub project: String,
|
||||
/// Filter the logs to only ones from specific services.
|
||||
/// If empty, will include logs from all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// The service name
|
||||
pub service: String,
|
||||
/// The search terms.
|
||||
pub terms: Vec<String>,
|
||||
/// And: Only lines matching all terms
|
||||
@@ -139,10 +135,8 @@ pub struct WriteCommitComposeContents {
|
||||
pub struct ComposePull {
|
||||
/// The stack to deploy
|
||||
pub stack: Stack,
|
||||
/// Filter to only pull specific services.
|
||||
/// If empty, will pull all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Only deploy one service
|
||||
pub service: Option<String>,
|
||||
/// If provided, use it to login in. Otherwise check periphery local registries.
|
||||
pub git_token: Option<String>,
|
||||
/// If provided, use it to login in. Otherwise check periphery local git providers.
|
||||
@@ -164,10 +158,8 @@ pub struct ComposePullResponse {
|
||||
pub struct ComposeUp {
|
||||
/// The stack to deploy
|
||||
pub stack: Stack,
|
||||
/// Filter to only deploy specific services.
|
||||
/// If empty, will deploy all services.
|
||||
#[serde(default)]
|
||||
pub services: Vec<String>,
|
||||
/// Only deploy one service
|
||||
pub service: Option<String>,
|
||||
/// If provided, use it to login in. Otherwise check periphery local registries.
|
||||
pub git_token: Option<String>,
|
||||
/// If provided, use it to login in. Otherwise check periphery local git providers.
|
||||
@@ -194,8 +186,6 @@ pub struct ComposeUpResponse {
|
||||
pub file_contents: Vec<FileContents>,
|
||||
/// The error in getting remote file contents at the path, or null
|
||||
pub remote_errors: Vec<FileContents>,
|
||||
/// The output of `docker compose config` at deploy time
|
||||
pub compose_config: Option<String>,
|
||||
/// If its a repo based stack, will include the latest commit hash
|
||||
pub commit_hash: Option<String>,
|
||||
/// If its a repo based stack, will include the latest commit message
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17
|
||||
image: postgres
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
@@ -27,7 +27,7 @@ services:
|
||||
- POSTGRES_DB=${KOMODO_DATABASE_DB_NAME:-komodo}
|
||||
|
||||
ferretdb:
|
||||
image: ghcr.io/ferretdb/ferretdb:1
|
||||
image: ghcr.io/ferretdb/ferretdb
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
services:
|
||||
ferretdb:
|
||||
image: ghcr.io/ferretdb/ferretdb:1
|
||||
image: ghcr.io/ferretdb/ferretdb
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -118,15 +118,6 @@ transparent_mode = false
|
||||
## Default: false
|
||||
disable_non_admin_create = false
|
||||
|
||||
## Normally users can update their username / password using the API.
|
||||
## This will disable this ability for specific users or all users.
|
||||
## Example:
|
||||
## - `lock_login_credentials_for = []` will allow all users to update username / password.
|
||||
## - `lock_login_credentials_for = ["demo"]` will block the demo user from doing so.
|
||||
## - `lock_login_credentials_for = ["__ALL__"]` will block all users.
|
||||
## Default: empty list
|
||||
lock_login_credentials_for = []
|
||||
|
||||
## Optionally provide a specific jwt secret.
|
||||
## Passing nothing or an empty string will cause one to be generated on every startup.
|
||||
## This means users will have to log in again if Komodo restarts.
|
||||
|
||||
@@ -15,8 +15,8 @@ Komodo is able to support Postgres and Sqlite by utilizing the [FerretDB Mongo A
|
||||
### First login
|
||||
|
||||
Core should now be accessible on the specified port, so navigating to `http://<address>:<port>` will display the login page.
|
||||
Enter your preferred admin username and password, and click **"Sign Up"**, _not_ "Log In", to create your admin user for Komodo.
|
||||
Any additional users to create accounts will be disabled by default, and must be enabled by an admin.
|
||||
|
||||
The first user to log in will be auto enabled and made an admin. Any additional users to create accounts will be disabled by default, and must be enabled by an admin.
|
||||
|
||||
### Https
|
||||
|
||||
|
||||
@@ -41,11 +41,13 @@ const config: Config = {
|
||||
{
|
||||
docs: {
|
||||
sidebarPath: "./sidebars.ts",
|
||||
editUrl: "https://github.com/moghtech/komodo/tree/main/docsite",
|
||||
editUrl:
|
||||
"https://github.com/moghtech/komodo/tree/main/docsite",
|
||||
},
|
||||
blog: {
|
||||
showReadingTime: true,
|
||||
editUrl: "https://github.com/moghtech/komodo/tree/main/docsite",
|
||||
editUrl:
|
||||
"https://github.com/moghtech/komodo/tree/main/docsite",
|
||||
},
|
||||
theme: {
|
||||
customCss: "./src/css/custom.css",
|
||||
@@ -76,12 +78,7 @@ const config: Config = {
|
||||
label: "docs",
|
||||
},
|
||||
{
|
||||
href: "https://opencollective.com/komodo",
|
||||
label: "Donate",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
href: "https://docs.rs/komodo_client/latest/komodo_client",
|
||||
href: "https://docs.rs/komodo_client/latest/komodo_client/",
|
||||
label: "Docs.rs",
|
||||
position: "right",
|
||||
},
|
||||
|
||||
Vendored
+1
-20
@@ -1,5 +1,5 @@
|
||||
import { AuthResponses, ExecuteResponses, ReadResponses, UserResponses, WriteResponses } from "./responses.js";
|
||||
import { AuthRequest, ExecuteRequest, ReadRequest, UpdateListItem, UserRequest, WriteRequest } from "./types.js";
|
||||
import { AuthRequest, ExecuteRequest, ReadRequest, UserRequest, WriteRequest } from "./types.js";
|
||||
export * as Types from "./types.js";
|
||||
type InitOptions = {
|
||||
type: "jwt";
|
||||
@@ -13,11 +13,6 @@ type InitOptions = {
|
||||
secret: string;
|
||||
};
|
||||
};
|
||||
export declare class CancelToken {
|
||||
cancelled: boolean;
|
||||
constructor();
|
||||
cancel(): void;
|
||||
}
|
||||
/** Initialize a new client for Komodo */
|
||||
export declare function KomodoClient(url: string, options: InitOptions): {
|
||||
/**
|
||||
@@ -93,18 +88,4 @@ export declare function KomodoClient(url: string, options: InitOptions): {
|
||||
}>>(type: T, params: Req["params"]) => Promise<ExecuteResponses[Req["type"]]>;
|
||||
/** Returns the version of Komodo Core the client is calling to. */
|
||||
core_version: () => Promise<string>;
|
||||
/**
|
||||
* Subscribes to the update websocket with automatic reconnect loop.
|
||||
*
|
||||
* Note. Awaiting this method will never finish.
|
||||
*/
|
||||
subscribe_to_update_websocket: ({ on_update, on_login, on_close, retry_timeout_ms, cancel, on_cancel, }: {
|
||||
on_update: (update: UpdateListItem) => void;
|
||||
on_login?: () => void;
|
||||
on_open?: () => void;
|
||||
on_close?: () => void;
|
||||
retry_timeout_ms?: number;
|
||||
cancel?: CancelToken;
|
||||
on_cancel?: () => void;
|
||||
}) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
export * as Types from "./types.js";
|
||||
export class CancelToken {
|
||||
cancelled;
|
||||
constructor() {
|
||||
this.cancelled = false;
|
||||
}
|
||||
cancel() {
|
||||
this.cancelled = true;
|
||||
}
|
||||
}
|
||||
/** Initialize a new client for Komodo */
|
||||
export function KomodoClient(url, options) {
|
||||
const state = {
|
||||
@@ -75,56 +66,6 @@ export function KomodoClient(url, options) {
|
||||
const write = async (type, params) => await request("/write", { type, params });
|
||||
const execute = async (type, params) => await request("/execute", { type, params });
|
||||
const core_version = () => read("GetVersion", {}).then((res) => res.version);
|
||||
const subscribe_to_update_websocket = async ({ on_update, on_login, on_close, retry_timeout_ms = 5_000, cancel = new CancelToken(), on_cancel, }) => {
|
||||
while (true) {
|
||||
if (cancel.cancelled) {
|
||||
on_cancel?.();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const ws = new WebSocket(url.replace("http", "ws") + "/ws/update");
|
||||
// Handle login on websocket open
|
||||
ws.addEventListener("open", () => {
|
||||
const login_msg = options.type === "jwt"
|
||||
? {
|
||||
type: "Jwt",
|
||||
params: {
|
||||
jwt: options.params.jwt,
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: "ApiKeys",
|
||||
params: {
|
||||
key: options.params.key,
|
||||
secret: options.params.secret,
|
||||
},
|
||||
};
|
||||
ws.send(JSON.stringify(login_msg));
|
||||
});
|
||||
ws.addEventListener("message", ({ data }) => {
|
||||
if (data == "LOGGED_IN")
|
||||
return on_login?.();
|
||||
on_update(JSON.parse(data));
|
||||
});
|
||||
if (on_close) {
|
||||
ws.addEventListener("close", on_close);
|
||||
}
|
||||
// This while loop will end when the socket is closed
|
||||
while (ws.readyState !== WebSocket.CLOSING &&
|
||||
ws.readyState !== WebSocket.CLOSED) {
|
||||
if (cancel.cancelled)
|
||||
ws.close();
|
||||
// Sleep for a bit before checking for websocket closed
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
// Sleep for a bit before retrying, maybe Komodo Core is down temporarily.
|
||||
await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms));
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
/**
|
||||
* Call the `/auth` api.
|
||||
@@ -189,11 +130,5 @@ export function KomodoClient(url, options) {
|
||||
execute,
|
||||
/** Returns the version of Komodo Core the client is calling to. */
|
||||
core_version,
|
||||
/**
|
||||
* Subscribes to the update websocket with automatic reconnect loop.
|
||||
*
|
||||
* Note. Awaiting this method will never finish.
|
||||
*/
|
||||
subscribe_to_update_websocket,
|
||||
};
|
||||
}
|
||||
|
||||
+2
-2
@@ -99,8 +99,8 @@ export type ReadResponses = {
|
||||
GetStack: Types.GetStackResponse;
|
||||
GetStackActionState: Types.GetStackActionStateResponse;
|
||||
GetStackWebhooksEnabled: Types.GetStackWebhooksEnabledResponse;
|
||||
GetStackLog: Types.GetStackLogResponse;
|
||||
SearchStackLog: Types.SearchStackLogResponse;
|
||||
GetStackServiceLog: Types.GetStackServiceLogResponse;
|
||||
SearchStackServiceLog: Types.SearchStackServiceLogResponse;
|
||||
ListStacks: Types.ListStacksResponse;
|
||||
ListFullStacks: Types.ListFullStacksResponse;
|
||||
ListStackServices: Types.ListStackServicesResponse;
|
||||
|
||||
Vendored
+47
-101
@@ -104,7 +104,7 @@ export interface ResourceQuery<T> {
|
||||
export interface ActionQuerySpecifics {
|
||||
}
|
||||
export type ActionQuery = ResourceQuery<ActionQuerySpecifics>;
|
||||
export type AlerterEndpoint =
|
||||
export type AlerterEndpoint =
|
||||
/** Send alert serialized to JSON to an http endpoint. */
|
||||
{
|
||||
type: "Custom";
|
||||
@@ -390,7 +390,7 @@ export interface BuildQuerySpecifics {
|
||||
built_since?: I64;
|
||||
}
|
||||
export type BuildQuery = ResourceQuery<BuildQuerySpecifics>;
|
||||
export type BuilderConfig =
|
||||
export type BuilderConfig =
|
||||
/** Use a Periphery address as a Builder. */
|
||||
{
|
||||
type: "Url";
|
||||
@@ -421,7 +421,7 @@ export interface BuilderQuerySpecifics {
|
||||
}
|
||||
export type BuilderQuery = ResourceQuery<BuilderQuerySpecifics>;
|
||||
/** A wrapper for all Komodo exections. */
|
||||
export type Execution =
|
||||
export type Execution =
|
||||
/** The "null" execution. Does nothing. */
|
||||
{
|
||||
type: "None";
|
||||
@@ -725,7 +725,7 @@ export interface JwtResponse {
|
||||
export type CreateLocalUserResponse = JwtResponse;
|
||||
export type CreateProcedureResponse = Procedure;
|
||||
export type CreateRepoWebhookResponse = NoData;
|
||||
export type UserConfig =
|
||||
export type UserConfig =
|
||||
/** User that logs in with username / password */
|
||||
{
|
||||
type: "Local";
|
||||
@@ -833,7 +833,7 @@ export type DeleteStackWebhookResponse = NoData;
|
||||
export type DeleteSyncWebhookResponse = NoData;
|
||||
export type DeleteUserResponse = User;
|
||||
export type DeleteVariableResponse = Variable;
|
||||
export type DeploymentImage =
|
||||
export type DeploymentImage =
|
||||
/** Deploy any external image. */
|
||||
{
|
||||
type: "Image";
|
||||
@@ -1010,7 +1010,7 @@ export declare enum SeverityLevel {
|
||||
Critical = "CRITICAL"
|
||||
}
|
||||
/** The variants of data related to the alert. */
|
||||
export type AlertData =
|
||||
export type AlertData =
|
||||
/** A null alert */
|
||||
{
|
||||
type: "None";
|
||||
@@ -1455,11 +1455,6 @@ export interface ResourceSyncConfig {
|
||||
* See [https://komo.do/docs/sync-resources](https://komo.do/docs/sync-resources) for more information.
|
||||
*/
|
||||
resource_path?: string[];
|
||||
/**
|
||||
* Excluse Komodo Resources (Servers / Stacks / Builds)
|
||||
* from the sync. Will be variable / user group only sync.
|
||||
*/
|
||||
exclude_resources?: boolean;
|
||||
/**
|
||||
* Enable "pushes" to the file,
|
||||
* which exports resources matching tags to single file.
|
||||
@@ -1481,7 +1476,7 @@ export interface ResourceSyncConfig {
|
||||
/** Manage the file contents in the UI. */
|
||||
file_contents?: string;
|
||||
}
|
||||
export type DiffData =
|
||||
export type DiffData =
|
||||
/** Resource will be created */
|
||||
{
|
||||
type: "Create";
|
||||
@@ -1646,7 +1641,7 @@ export interface ServerConfig {
|
||||
}
|
||||
export type Server = Resource<ServerConfig, undefined>;
|
||||
export type GetServerResponse = Server;
|
||||
export type ServerTemplateConfig =
|
||||
export type ServerTemplateConfig =
|
||||
/** Template to launch an AWS EC2 instance */
|
||||
{
|
||||
type: "Aws";
|
||||
@@ -1670,7 +1665,6 @@ export interface StackActionState {
|
||||
destroying: boolean;
|
||||
}
|
||||
export type GetStackActionStateResponse = StackActionState;
|
||||
export type GetStackLogResponse = Log;
|
||||
/** The compose file configuration. */
|
||||
export interface StackConfig {
|
||||
/** The server to deploy the stack on. */
|
||||
@@ -1705,13 +1699,6 @@ export interface StackConfig {
|
||||
* enable both.
|
||||
*/
|
||||
auto_update?: boolean;
|
||||
/**
|
||||
* If auto update is enabled, Komodo will
|
||||
* by default only update the specific services
|
||||
* with image updates. If this parameter is set to true,
|
||||
* Komodo will redeploy the whole Stack (all services).
|
||||
*/
|
||||
auto_update_all_services?: boolean;
|
||||
/** Whether to run `docker compose down` before `compose up`. */
|
||||
destroy_before_deploy?: boolean;
|
||||
/** Whether to skip secret interpolation into the stack environment variables. */
|
||||
@@ -1788,7 +1775,7 @@ export interface StackConfig {
|
||||
registry_account?: string;
|
||||
/** The optional command to run before the Stack is deployed. */
|
||||
pre_deploy?: SystemCommand;
|
||||
/** The optional command to run after the Stack is deployed. */
|
||||
/** The optional command to run before the Stack is deployed. */
|
||||
post_deploy?: SystemCommand;
|
||||
/**
|
||||
* The extra arguments to pass after `docker compose up -d`.
|
||||
@@ -1870,21 +1857,13 @@ export interface StackInfo {
|
||||
deployed_hash?: string;
|
||||
/** Deployed commit message, or null. Only for repo based stacks */
|
||||
deployed_message?: string;
|
||||
/**
|
||||
* The deployed compose file contents.
|
||||
* This is updated whenever Komodo successfully deploys the stack.
|
||||
*/
|
||||
/** The deployed compose file contents. This is updated whenever Komodo successfully deploys the stack. */
|
||||
deployed_contents?: FileContents[];
|
||||
/**
|
||||
* The deployed service names.
|
||||
* This is updated whenever it is empty, or deployed contents is updated.
|
||||
*/
|
||||
deployed_services?: StackServiceNames[];
|
||||
/**
|
||||
* The output of `docker compose config`.
|
||||
* This is updated whenever Komodo successfully deploys the stack.
|
||||
*/
|
||||
deployed_config?: string;
|
||||
/**
|
||||
* The latest service names.
|
||||
* This is updated whenever the stack cache refreshes, using the latest file contents (either db defined or remote).
|
||||
@@ -1905,6 +1884,7 @@ export interface StackInfo {
|
||||
}
|
||||
export type Stack = Resource<StackConfig, StackInfo>;
|
||||
export type GetStackResponse = Stack;
|
||||
export type GetStackServiceLogResponse = Log;
|
||||
/** System information of a server */
|
||||
export interface SystemInformation {
|
||||
/** The system name */
|
||||
@@ -3057,8 +3037,8 @@ export interface ProviderAccount {
|
||||
export interface DockerRegistry {
|
||||
/** The docker provider domain. Default: `docker.io`. */
|
||||
domain: string;
|
||||
/** The accounts on the registry. Required. */
|
||||
accounts: ProviderAccount[];
|
||||
/** The account username. Required. */
|
||||
accounts?: ProviderAccount[];
|
||||
/**
|
||||
* Available organizations on the registry provider.
|
||||
* Used to push an image under an organization's repo rather than an account's repo.
|
||||
@@ -3097,11 +3077,11 @@ export interface GitProvider {
|
||||
domain: string;
|
||||
/** Whether to use https. Default: true. */
|
||||
https: boolean;
|
||||
/** The accounts on the git provider. Required. */
|
||||
/** The account username. Required. */
|
||||
accounts: ProviderAccount[];
|
||||
}
|
||||
export type ListGitProvidersFromConfigResponse = GitProvider[];
|
||||
export type UserTarget =
|
||||
export type UserTarget =
|
||||
/** User Id */
|
||||
{
|
||||
type: "User";
|
||||
@@ -3388,7 +3368,7 @@ export interface ResourceSyncQuerySpecifics {
|
||||
export type ResourceSyncQuery = ResourceQuery<ResourceSyncQuerySpecifics>;
|
||||
export type SearchContainerLogResponse = Log;
|
||||
export type SearchDeploymentLogResponse = Log;
|
||||
export type SearchStackLogResponse = Log;
|
||||
export type SearchStackServiceLogResponse = Log;
|
||||
export interface ServerQuerySpecifics {
|
||||
}
|
||||
/** Server-specific query */
|
||||
@@ -4455,11 +4435,8 @@ export interface Deploy {
|
||||
export interface DeployStack {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter to only deploy specific services.
|
||||
* If empty, will deploy all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to "compose up" */
|
||||
service?: string;
|
||||
/**
|
||||
* Override the default termination max time.
|
||||
* Only used if the stack needs to be taken down first.
|
||||
@@ -4514,11 +4491,8 @@ export interface DestroyDeployment {
|
||||
export interface DestroyStack {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter to only destroy specific services.
|
||||
* If empty, will destroy all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to destroy */
|
||||
service?: string;
|
||||
/** Pass `--remove-orphans` */
|
||||
remove_orphans?: boolean;
|
||||
/** Override the default termination max time. */
|
||||
@@ -5111,19 +5085,12 @@ export interface GetStackActionState {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
}
|
||||
/**
|
||||
* Get a stack's logs. Filter down included services. Response: [GetStackLogResponse].
|
||||
*
|
||||
* Note. This call will hit the underlying server directly for most up to date log.
|
||||
*/
|
||||
export interface GetStackLog {
|
||||
/** Get a stack service's log. Response: [GetStackServiceLogResponse]. */
|
||||
export interface GetStackServiceLog {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter the logs to only ones from specific services.
|
||||
* If empty, will include logs from all services.
|
||||
*/
|
||||
services: string[];
|
||||
/** The service to get the log for. */
|
||||
service: string;
|
||||
/**
|
||||
* The number of lines of the log tail to include.
|
||||
* Default: 100.
|
||||
@@ -5762,7 +5729,7 @@ export interface ListStackServices {
|
||||
}
|
||||
/** List stacks matching optional query. Response: [ListStacksResponse]. */
|
||||
export interface ListStacks {
|
||||
/** optional structured query to filter stacks. */
|
||||
/** optional structured query to filter syncs. */
|
||||
query?: StackQuery;
|
||||
}
|
||||
/**
|
||||
@@ -5916,11 +5883,8 @@ export interface PauseDeployment {
|
||||
export interface PauseStack {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter to only pause specific services.
|
||||
* If empty, will pause all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to pause */
|
||||
service?: string;
|
||||
}
|
||||
export interface PermissionToml {
|
||||
/**
|
||||
@@ -6038,11 +6002,8 @@ export interface PullRepo {
|
||||
export interface PullStack {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter to only pull specific services.
|
||||
* If empty, will pull all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to start */
|
||||
service?: string;
|
||||
}
|
||||
/**
|
||||
* Push a resource to the front of the users 10 most recently viewed resources.
|
||||
@@ -6287,11 +6248,8 @@ export interface RestartDeployment {
|
||||
export interface RestartStack {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter to only restart specific services.
|
||||
* If empty, will restart all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to restart */
|
||||
service?: string;
|
||||
}
|
||||
/** Runs the target Action. Response: [Update] */
|
||||
export interface RunAction {
|
||||
@@ -6384,19 +6342,16 @@ export interface SearchDeploymentLog {
|
||||
timestamps?: boolean;
|
||||
}
|
||||
/**
|
||||
* Search the stack log's tail using `grep`. All lines go to stdout.
|
||||
* Response: [SearchStackLogResponse].
|
||||
* Search the deployment log's tail using `grep`. All lines go to stdout.
|
||||
* Response: [Log].
|
||||
*
|
||||
* Note. This call will hit the underlying server directly for most up to date log.
|
||||
*/
|
||||
export interface SearchStackLog {
|
||||
export interface SearchStackServiceLog {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter the logs to only ones from specific services.
|
||||
* If empty, will include logs from all services.
|
||||
*/
|
||||
services: string[];
|
||||
/** The service to get the log for. */
|
||||
service: string;
|
||||
/** The terms to search for. */
|
||||
terms: string[];
|
||||
/**
|
||||
@@ -6491,11 +6446,8 @@ export interface StartDeployment {
|
||||
export interface StartStack {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter to only start specific services.
|
||||
* If empty, will start all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to start */
|
||||
service?: string;
|
||||
}
|
||||
/** Stops all containers on the target server. Response: [Update] */
|
||||
export interface StopAllContainers {
|
||||
@@ -6536,11 +6488,8 @@ export interface StopStack {
|
||||
stack: string;
|
||||
/** Override the default termination max time. */
|
||||
stop_time?: number;
|
||||
/**
|
||||
* Filter to only stop specific services.
|
||||
* If empty, will stop all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to stop */
|
||||
service?: string;
|
||||
}
|
||||
export interface TerminationSignalLabel {
|
||||
signal: TerminationSignal;
|
||||
@@ -6595,11 +6544,8 @@ export interface UnpauseDeployment {
|
||||
export interface UnpauseStack {
|
||||
/** Id or name */
|
||||
stack: string;
|
||||
/**
|
||||
* Filter to only unpause specific services.
|
||||
* If empty, will unpause all services.
|
||||
*/
|
||||
services?: string[];
|
||||
/** Optionally specify a specific service to unpause */
|
||||
service?: string;
|
||||
}
|
||||
/**
|
||||
* Update the action at the given id, and return the updated action.
|
||||
@@ -7147,7 +7093,7 @@ export type ExecuteRequest = {
|
||||
params: RunSync;
|
||||
};
|
||||
/** Configuration for the registry to push the built image to. */
|
||||
export type ImageRegistryLegacy1_14 =
|
||||
export type ImageRegistryLegacy1_14 =
|
||||
/** Don't push the image to any registry */
|
||||
{
|
||||
type: "None";
|
||||
@@ -7417,11 +7363,11 @@ export type ReadRequest = {
|
||||
type: "GetStackWebhooksEnabled";
|
||||
params: GetStackWebhooksEnabled;
|
||||
} | {
|
||||
type: "GetStackLog";
|
||||
params: GetStackLog;
|
||||
type: "GetStackServiceLog";
|
||||
params: GetStackServiceLog;
|
||||
} | {
|
||||
type: "SearchStackLog";
|
||||
params: SearchStackLog;
|
||||
type: "SearchStackServiceLog";
|
||||
params: SearchStackServiceLog;
|
||||
} | {
|
||||
type: "ListStacks";
|
||||
params: ListStacks;
|
||||
|
||||
@@ -19,7 +19,7 @@ export const TopbarAlerts = () => {
|
||||
const { data } = useRead(
|
||||
"ListAlerts",
|
||||
{ query: { resolved: false } },
|
||||
{ refetchInterval: 3_000 }
|
||||
{ refetchInterval: 10_000 }
|
||||
);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ConfigLayout = <
|
||||
T extends Types.Resource<unknown, unknown>["config"],
|
||||
>({
|
||||
original,
|
||||
update,
|
||||
config,
|
||||
children,
|
||||
disabled,
|
||||
onConfirm,
|
||||
@@ -35,7 +35,7 @@ export const ConfigLayout = <
|
||||
file_contents_language,
|
||||
}: {
|
||||
original: T;
|
||||
update: Partial<T>;
|
||||
config: Partial<T>;
|
||||
children: ReactNode;
|
||||
disabled: boolean;
|
||||
onConfirm: () => void;
|
||||
@@ -47,7 +47,7 @@ export const ConfigLayout = <
|
||||
const titleProps = titleOther
|
||||
? { titleOther }
|
||||
: { title: "Config", icon: <Settings className="w-4 h-4" /> };
|
||||
const changesMade = Object.keys(update).length ? true : false;
|
||||
const changesMade = Object.keys(config).length ? true : false;
|
||||
return (
|
||||
<Section
|
||||
{...titleProps}
|
||||
@@ -61,25 +61,24 @@ export const ConfigLayout = <
|
||||
)}
|
||||
{selector}
|
||||
{changesMade && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onReset}
|
||||
disabled={disabled || !changesMade}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
Reset
|
||||
</Button>
|
||||
<ConfirmUpdate
|
||||
previous={original}
|
||||
content={update}
|
||||
onConfirm={async () => onConfirm()}
|
||||
disabled={disabled}
|
||||
file_contents_language={file_contents_language}
|
||||
key_listener
|
||||
/>
|
||||
</>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onReset}
|
||||
disabled={disabled || !changesMade}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
Reset
|
||||
</Button>
|
||||
)}
|
||||
{changesMade && (
|
||||
<ConfirmUpdate
|
||||
previous={original}
|
||||
content={config}
|
||||
onConfirm={async () => onConfirm()}
|
||||
disabled={disabled}
|
||||
file_contents_language={file_contents_language}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
@@ -115,7 +114,7 @@ export type ConfigComponent<T> = {
|
||||
};
|
||||
|
||||
export const Config = <T,>({
|
||||
original,
|
||||
config,
|
||||
update,
|
||||
disabled,
|
||||
disableSidebar,
|
||||
@@ -126,7 +125,7 @@ export const Config = <T,>({
|
||||
titleOther,
|
||||
file_contents_language,
|
||||
}: {
|
||||
original: T;
|
||||
config: T;
|
||||
update: Partial<T>;
|
||||
disabled: boolean;
|
||||
disableSidebar?: boolean;
|
||||
@@ -141,20 +140,18 @@ export const Config = <T,>({
|
||||
file_contents_language?: MonacoLanguage;
|
||||
}) => {
|
||||
const sections = keys(components).filter((section) => !!components[section]);
|
||||
const changesMade = Object.keys(update).length ? true : false;
|
||||
const onConfirm = async () => {
|
||||
await onSave();
|
||||
set({});
|
||||
};
|
||||
const onReset = () => set({});
|
||||
|
||||
return (
|
||||
<ConfigLayout
|
||||
original={original}
|
||||
original={config}
|
||||
titleOther={titleOther}
|
||||
update={update}
|
||||
config={update}
|
||||
disabled={disabled}
|
||||
onConfirm={onConfirm}
|
||||
onReset={onReset}
|
||||
onConfirm={async () => {
|
||||
await onSave();
|
||||
set({});
|
||||
}}
|
||||
onReset={() => set({})}
|
||||
selector={selector}
|
||||
file_contents_language={file_contents_language}
|
||||
>
|
||||
@@ -191,24 +188,6 @@ export const Config = <T,>({
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-col gap-2">
|
||||
<ConfirmUpdate
|
||||
previous={original}
|
||||
content={update}
|
||||
onConfirm={onConfirm}
|
||||
disabled={disabled || !changesMade}
|
||||
file_contents_language={file_contents_language}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onReset}
|
||||
disabled={disabled || !changesMade}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -296,7 +275,7 @@ export const Config = <T,>({
|
||||
)}
|
||||
{!contentHidden && (
|
||||
<ConfigAgain
|
||||
config={original}
|
||||
config={config}
|
||||
update={update}
|
||||
set={(u) => set((p) => ({ ...p, ...u }))}
|
||||
components={components}
|
||||
@@ -310,30 +289,6 @@ export const Config = <T,>({
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{changesMade && (
|
||||
<div className="flex gap-2 justify-end">
|
||||
<div className="text-muted-foreground flex items-center gap-2">
|
||||
<AlertTriangle className="w-4 h-4" /> Unsaved changes
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onReset}
|
||||
disabled={disabled}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
Reset
|
||||
</Button>
|
||||
<ConfirmUpdate
|
||||
previous={original}
|
||||
content={update}
|
||||
onConfirm={onConfirm}
|
||||
disabled={disabled}
|
||||
file_contents_language={file_contents_language}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ConfigLayout>
|
||||
|
||||
@@ -600,7 +600,6 @@ interface ConfirmUpdateProps<T> {
|
||||
disabled: boolean;
|
||||
language?: MonacoLanguage;
|
||||
file_contents_language?: MonacoLanguage;
|
||||
key_listener?: boolean;
|
||||
}
|
||||
|
||||
export function ConfirmUpdate<T>({
|
||||
@@ -611,11 +610,9 @@ export function ConfirmUpdate<T>({
|
||||
disabled,
|
||||
language,
|
||||
file_contents_language,
|
||||
key_listener = false,
|
||||
}: ConfirmUpdateProps<T>) {
|
||||
const [open, set] = useState(false);
|
||||
useCtrlKeyListener("Enter", () => {
|
||||
if (!key_listener) return;
|
||||
if (open) {
|
||||
onConfirm();
|
||||
} else {
|
||||
@@ -628,7 +625,7 @@ export function ConfirmUpdate<T>({
|
||||
<Button
|
||||
onClick={() => set(true)}
|
||||
disabled={disabled}
|
||||
className="flex items-center gap-2 w-100"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Save className="w-4 h-4" />
|
||||
Save
|
||||
|
||||
@@ -1,174 +1,9 @@
|
||||
import { logToHtml } from "@lib/utils";
|
||||
import { Types } from "komodo_client";
|
||||
import { Button } from "@ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@ui/select";
|
||||
import {
|
||||
AlertOctagon,
|
||||
ChevronDown,
|
||||
RefreshCw,
|
||||
ScrollText,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { Section } from "./layouts";
|
||||
import { Switch } from "@ui/switch";
|
||||
import { Input } from "@ui/input";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@ui/toggle-group";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { useLocalStorage } from "@lib/hooks";
|
||||
|
||||
export type LogStream = "stdout" | "stderr";
|
||||
|
||||
export const LogSection = ({
|
||||
regular_logs,
|
||||
search_logs,
|
||||
titleOther,
|
||||
extraParams,
|
||||
}: {
|
||||
regular_logs: (
|
||||
timestamps: boolean,
|
||||
stream: LogStream,
|
||||
tail: number
|
||||
) => {
|
||||
Log: ReactNode;
|
||||
refetch: () => void;
|
||||
stderr: boolean;
|
||||
};
|
||||
search_logs: (
|
||||
timestamps: boolean,
|
||||
terms: string[],
|
||||
invert: boolean
|
||||
) => { Log: ReactNode; refetch: () => void; stderr: boolean };
|
||||
titleOther?: ReactNode;
|
||||
extraParams?: ReactNode;
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const [timestamps, setTimestamps] = useLocalStorage(
|
||||
"log-timestamps-v1",
|
||||
false
|
||||
);
|
||||
const [stream, setStream] = useState<LogStream>("stdout");
|
||||
const [tail, set] = useState("100");
|
||||
const [terms, setTerms] = useState<string[]>([]);
|
||||
const [invert, setInvert] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [poll, setPoll] = useLocalStorage("log-poll-v1", false);
|
||||
|
||||
const addTerm = () => {
|
||||
if (!search.length) return;
|
||||
if (terms.includes(search)) {
|
||||
toast({ title: "Search term is already present" });
|
||||
setSearch("");
|
||||
return;
|
||||
}
|
||||
setTerms([...terms, search]);
|
||||
setSearch("");
|
||||
};
|
||||
|
||||
const clearSearch = () => {
|
||||
setSearch("");
|
||||
setTerms([]);
|
||||
};
|
||||
|
||||
const { Log, refetch, stderr } = terms.length
|
||||
? search_logs(timestamps, terms, invert)
|
||||
: regular_logs(timestamps, stream, Number(tail));
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={titleOther ? undefined : "Log"}
|
||||
icon={titleOther ? undefined : <ScrollText className="w-4 h-4" />}
|
||||
titleOther={titleOther}
|
||||
itemsCenterTitleRow
|
||||
actions={
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-muted-foreground flex gap-1 text-sm">
|
||||
Invert
|
||||
</div>
|
||||
<Switch checked={invert} onCheckedChange={setInvert} />
|
||||
</div>
|
||||
{terms.map((term, index) => (
|
||||
<Button
|
||||
key={term}
|
||||
variant="destructive"
|
||||
onClick={() => setTerms(terms.filter((_, i) => i !== index))}
|
||||
className="flex gap-2 items-center py-0 px-2"
|
||||
>
|
||||
{term}
|
||||
<X className="w-4 h-h" />
|
||||
</Button>
|
||||
))}
|
||||
<div className="relative">
|
||||
<Input
|
||||
placeholder="Search Logs"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onBlur={addTerm}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") addTerm();
|
||||
}}
|
||||
className="w-[180px] xl:w-[240px]"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={clearSearch}
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={stream}
|
||||
onValueChange={setStream as any}
|
||||
>
|
||||
<ToggleGroupItem value="stdout">stdout</ToggleGroupItem>
|
||||
<ToggleGroupItem value="stderr">
|
||||
stderr
|
||||
{stderr && (
|
||||
<AlertOctagon className="w-4 h-4 ml-2 stroke-red-500" />
|
||||
)}
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
<Button variant="secondary" size="icon" onClick={() => refetch()}>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</Button>
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => setTimestamps((t) => !t)}
|
||||
>
|
||||
<div className="text-muted-foreground text-sm">Timestamps</div>
|
||||
<Switch checked={timestamps} />
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => setPoll((p) => !p)}
|
||||
>
|
||||
<div className="text-muted-foreground text-sm">Poll</div>
|
||||
<Switch checked={poll} />
|
||||
</div>
|
||||
<TailLengthSelector
|
||||
selected={tail}
|
||||
onSelect={set}
|
||||
disabled={search.length > 0}
|
||||
/>
|
||||
{extraParams}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{Log}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@ui/select";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export const Log = ({
|
||||
log,
|
||||
|
||||
@@ -46,7 +46,7 @@ export const ActionConfig = ({ id }: { id: string }) => {
|
||||
<Config
|
||||
disabled={disabled}
|
||||
disableSidebar
|
||||
original={config}
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={async () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ export const AlerterConfig = ({ id }: { id: string }) => {
|
||||
return (
|
||||
<Config
|
||||
disabled={disabled}
|
||||
original={config}
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={async () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user