forked from github-starred/komodo
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f45205011e | ||
|
|
5b211fb8f0 | ||
|
|
3f04614303 | ||
|
|
4c3210f70a | ||
|
|
2c37ac26a5 | ||
|
|
db1cf786ac |
Generated
+696
-629
File diff suppressed because it is too large
Load Diff
+19
-19
@@ -8,7 +8,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.17.0-dev-7"
|
||||
version = "1.17.1"
|
||||
edition = "2024"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -30,7 +30,7 @@ git = { path = "lib/git" }
|
||||
# MOGH
|
||||
run_command = { version = "0.0.6", features = ["async_tokio"] }
|
||||
serror = { version = "0.5.0", default-features = false }
|
||||
slack = { version = "0.3.0", package = "slack_client_rs", default-features = false, features = ["rustls"] }
|
||||
slack = { version = "0.4.0", package = "slack_client_rs", default-features = false, features = ["rustls"] }
|
||||
derive_default_builder = "0.1.8"
|
||||
derive_empty_traits = "0.1.0"
|
||||
merge_config_files = "0.1.5"
|
||||
@@ -44,9 +44,9 @@ mungos = "3.2.0"
|
||||
svi = "1.0.1"
|
||||
|
||||
# ASYNC
|
||||
reqwest = { version = "0.12.12", default-features = false, features = ["json", "rustls-tls-native-roots"] }
|
||||
tokio = { version = "1.44.0", features = ["full"] }
|
||||
tokio-util = "0.7.13"
|
||||
reqwest = { version = "0.12.15", default-features = false, features = ["json", "rustls-tls-native-roots"] }
|
||||
tokio = { version = "1.44.1", features = ["full"] }
|
||||
tokio-util = "0.7.14"
|
||||
futures = "0.3.31"
|
||||
futures-util = "0.3.31"
|
||||
arc-swap = "1.7.1"
|
||||
@@ -54,7 +54,7 @@ arc-swap = "1.7.1"
|
||||
# SERVER
|
||||
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-server = { version = "0.7.2", features = ["tls-rustls"] }
|
||||
axum = { version = "0.8.1", features = ["ws", "json", "macros"] }
|
||||
tokio-tungstenite = "0.26.2"
|
||||
|
||||
@@ -71,28 +71,28 @@ anyhow = "1.0.97"
|
||||
thiserror = "2.0.12"
|
||||
|
||||
# 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.29.0", features = ["tls-roots", "reqwest-rustls"] }
|
||||
opentelemetry_sdk = { version = "0.29.0", 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.29.0"
|
||||
tracing-opentelemetry = "0.30.0"
|
||||
opentelemetry = "0.29.0"
|
||||
tracing = "0.1.41"
|
||||
|
||||
# CONFIG
|
||||
clap = { version = "4.5.32", features = ["derive"] }
|
||||
clap = { version = "4.5.36", features = ["derive"] }
|
||||
dotenvy = "0.15.7"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO / AUTH
|
||||
uuid = { version = "1.15.1", features = ["v4", "fast-rng", "serde"] }
|
||||
uuid = { version = "1.16.0", features = ["v4", "fast-rng", "serde"] }
|
||||
jsonwebtoken = { version = "9.3.1", default-features = false }
|
||||
openidconnect = "4.0.0"
|
||||
urlencoding = "2.1.3"
|
||||
nom_pem = "4.0.0"
|
||||
bcrypt = "0.17.0"
|
||||
base64 = "0.22.1"
|
||||
rustls = "0.23.23"
|
||||
rustls = "0.23.26"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.8"
|
||||
rand = "0.9.0"
|
||||
@@ -100,19 +100,19 @@ hex = "0.4.3"
|
||||
|
||||
# SYSTEM
|
||||
bollard = "0.18.1"
|
||||
sysinfo = "0.33.1"
|
||||
sysinfo = "0.34.2"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.6.0"
|
||||
aws-sdk-ec2 = "1.117.0"
|
||||
aws-config = "1.6.1"
|
||||
aws-sdk-ec2 = "1.121.1"
|
||||
aws-credential-types = "1.2.2"
|
||||
|
||||
# MISC
|
||||
derive_builder = "0.20.2"
|
||||
typeshare = "1.0.4"
|
||||
octorust = "0.9.0"
|
||||
octorust = "0.10.0"
|
||||
dashmap = "6.1.0"
|
||||
wildcard = "0.3.0"
|
||||
colored = "3.0.0"
|
||||
regex = "1.11.1"
|
||||
bson = "2.13.0"
|
||||
bson = "2.14.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Builds the Komodo Core and Periphery binaries
|
||||
## for a specific architecture.
|
||||
|
||||
FROM rust:1.85.0-bullseye AS builder
|
||||
FROM rust:1.85.1-bullseye AS builder
|
||||
|
||||
WORKDIR /builder
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
# Build Core
|
||||
FROM rust:1.85.0-bullseye AS core-builder
|
||||
FROM rust:1.85.1-bullseye AS core-builder
|
||||
|
||||
WORKDIR /builder
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
@@ -192,7 +192,31 @@ pub async fn send_alert(
|
||||
AlertData::None {} => Default::default(),
|
||||
};
|
||||
if !content.is_empty() {
|
||||
send_message(url, &content).await?;
|
||||
let vars_and_secrets = get_variables_and_secrets().await?;
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
let mut url_interpolated = url.to_string();
|
||||
|
||||
// interpolate variables and secrets into the url
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut url_interpolated,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
send_message(&url_interpolated, &content)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let replacers =
|
||||
secret_replacers.into_iter().collect::<Vec<_>>();
|
||||
let sanitized_error =
|
||||
svi::replace_in_string(&format!("{e:?}"), &replacers);
|
||||
anyhow::Error::msg(format!(
|
||||
"Error with slack request: {}",
|
||||
sanitized_error
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::{config::core_config, state::db_client};
|
||||
|
||||
mod discord;
|
||||
mod slack;
|
||||
mod ntfy;
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn send_alerts(alerts: &[Alert]) {
|
||||
@@ -128,6 +129,11 @@ pub async fn send_alert_to_alerter(
|
||||
)
|
||||
})
|
||||
}
|
||||
AlerterEndpoint::Ntfy(NtfyAlerterEndpoint { url }) => {
|
||||
ntfy::send_alert(url, alert).await.with_context(|| {
|
||||
format!("Failed to send alert to ntfy Alerter {}", alerter.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn send_alert(
|
||||
url: &str,
|
||||
alert: &Alert,
|
||||
) -> anyhow::Result<()> {
|
||||
let level = fmt_level(alert.level);
|
||||
let content = match &alert.data {
|
||||
AlertData::Test { id, name } => {
|
||||
let link = resource_link(ResourceTargetVariant::Alerter, id);
|
||||
format!(
|
||||
"{level} | If you see this message, then Alerter {} is working\n{link}",
|
||||
name,
|
||||
)
|
||||
}
|
||||
AlertData::ServerUnreachable {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
err,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let link = resource_link(ResourceTargetVariant::Server, id);
|
||||
match alert.level {
|
||||
SeverityLevel::Ok => {
|
||||
format!(
|
||||
"{level} | {}{} is now reachable\n{link}",
|
||||
name, region
|
||||
)
|
||||
}
|
||||
SeverityLevel::Critical => {
|
||||
let err = err
|
||||
.as_ref()
|
||||
.map(|e| format!("\nerror: {:#?}", e))
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
"{level} | {}{} is unreachable ❌\n{link}{err}",
|
||||
name, region
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
AlertData::ServerCpu {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
percentage,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let link = resource_link(ResourceTargetVariant::Server, id);
|
||||
format!(
|
||||
"{level} | {}{} cpu usage at {percentage:.1}%\n{link}",
|
||||
name, region,
|
||||
)
|
||||
}
|
||||
AlertData::ServerMem {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
used_gb,
|
||||
total_gb,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let link = resource_link(ResourceTargetVariant::Server, id);
|
||||
let percentage = 100.0 * used_gb / total_gb;
|
||||
format!(
|
||||
"{level} | {}{} memory usage at {percentage:.1}%💾\n\nUsing {used_gb:.1} GiB / {total_gb:.1} GiB\n{link}",
|
||||
name, region,
|
||||
)
|
||||
}
|
||||
AlertData::ServerDisk {
|
||||
id,
|
||||
name,
|
||||
region,
|
||||
path,
|
||||
used_gb,
|
||||
total_gb,
|
||||
} => {
|
||||
let region = fmt_region(region);
|
||||
let link = resource_link(ResourceTargetVariant::Server, id);
|
||||
let percentage = 100.0 * used_gb / total_gb;
|
||||
format!(
|
||||
"{level} | {}{} disk usage at {percentage:.1}%💿\nmount point: {:?}\nusing {used_gb:.1} GiB / {total_gb:.1} GiB\n{link}",
|
||||
name, region, path,
|
||||
)
|
||||
}
|
||||
AlertData::ContainerStateChange {
|
||||
id,
|
||||
name,
|
||||
server_id: _server_id,
|
||||
server_name,
|
||||
from,
|
||||
to,
|
||||
} => {
|
||||
let link = resource_link(ResourceTargetVariant::Deployment, id);
|
||||
let to_state = fmt_docker_container_state(to);
|
||||
format!(
|
||||
"📦Deployment {} is now {}\nserver: {}\nprevious: {}\n{link}",
|
||||
name, to_state, server_name, from,
|
||||
)
|
||||
}
|
||||
AlertData::DeploymentImageUpdateAvailable {
|
||||
id,
|
||||
name,
|
||||
server_id: _server_id,
|
||||
server_name,
|
||||
image,
|
||||
} => {
|
||||
let link = resource_link(ResourceTargetVariant::Deployment, id);
|
||||
format!(
|
||||
"⬆ Deployment {} has an update available\nserver: {}\nimage: {}\n{link}",
|
||||
name, server_name, image,
|
||||
)
|
||||
}
|
||||
AlertData::DeploymentAutoUpdated {
|
||||
id,
|
||||
name,
|
||||
server_id: _server_id,
|
||||
server_name,
|
||||
image,
|
||||
} => {
|
||||
let link = resource_link(ResourceTargetVariant::Deployment, id);
|
||||
format!(
|
||||
"⬆ Deployment {} was updated automatically\nserver: {}\nimage: {}\n{link}",
|
||||
name, server_name, image,
|
||||
)
|
||||
}
|
||||
AlertData::StackStateChange {
|
||||
id,
|
||||
name,
|
||||
server_id: _server_id,
|
||||
server_name,
|
||||
from,
|
||||
to,
|
||||
} => {
|
||||
let link = resource_link(ResourceTargetVariant::Stack, id);
|
||||
let to_state = fmt_stack_state(to);
|
||||
format!(
|
||||
"🥞 Stack {} is now {}\nserver: {}\nprevious: {}\n{link}",
|
||||
name, to_state, server_name, from,
|
||||
)
|
||||
}
|
||||
AlertData::StackImageUpdateAvailable {
|
||||
id,
|
||||
name,
|
||||
server_id: _server_id,
|
||||
server_name,
|
||||
service,
|
||||
image,
|
||||
} => {
|
||||
let link = resource_link(ResourceTargetVariant::Stack, id);
|
||||
format!(
|
||||
"⬆ Stack {} has an update available\nserver: {}\nservice: {}\nimage: {}\n{link}",
|
||||
name, server_name, service, image,
|
||||
)
|
||||
}
|
||||
AlertData::StackAutoUpdated {
|
||||
id,
|
||||
name,
|
||||
server_id: _server_id,
|
||||
server_name,
|
||||
images,
|
||||
} => {
|
||||
let link = resource_link(ResourceTargetVariant::Stack, id);
|
||||
let images_label =
|
||||
if images.len() > 1 { "images" } else { "image" };
|
||||
let images_str = images.join(", ");
|
||||
format!(
|
||||
"⬆ Stack {} was updated automatically ⏫\nserver: {}\n{}: {}\n{link}",
|
||||
name, server_name, images_label, images_str,
|
||||
)
|
||||
}
|
||||
AlertData::AwsBuilderTerminationFailed {
|
||||
instance_id,
|
||||
message,
|
||||
} => {
|
||||
format!(
|
||||
"{level} | Failed to terminate AWS builder instance\ninstance id: {}\n{}",
|
||||
instance_id, message,
|
||||
)
|
||||
}
|
||||
AlertData::ResourceSyncPendingUpdates { id, name } => {
|
||||
let link =
|
||||
resource_link(ResourceTargetVariant::ResourceSync, id);
|
||||
format!(
|
||||
"{level} | Pending resource sync updates on {}\n{link}",
|
||||
name,
|
||||
)
|
||||
}
|
||||
AlertData::BuildFailed { id, name, version } => {
|
||||
let link = resource_link(ResourceTargetVariant::Build, id);
|
||||
format!(
|
||||
"{level} | Build {} failed\nversion: v{}\n{link}",
|
||||
name, version,
|
||||
)
|
||||
}
|
||||
AlertData::RepoBuildFailed { id, name } => {
|
||||
let link = resource_link(ResourceTargetVariant::Repo, id);
|
||||
format!("{level} | Repo build for {} failed\n{link}", name,)
|
||||
}
|
||||
AlertData::None {} => Default::default(),
|
||||
};
|
||||
|
||||
if !content.is_empty() {
|
||||
send_message(url, content).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_message(
|
||||
url: &str,
|
||||
content: String,
|
||||
) -> anyhow::Result<()> {
|
||||
let response = http_client()
|
||||
.post(url)
|
||||
.header("Title", "ntfy Alert")
|
||||
.body(content)
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to send message")?;
|
||||
|
||||
let status = response.status();
|
||||
if status.is_success() {
|
||||
debug!("ntfy alert sent successfully: {}", status);
|
||||
Ok(())
|
||||
} else {
|
||||
let text = response.text().await.with_context(|| {
|
||||
format!(
|
||||
"Failed to send message to ntfy | {} | failed to get response text",
|
||||
status
|
||||
)
|
||||
})?;
|
||||
Err(anyhow!(
|
||||
"Failed to send message to ntfy | {} | {}",
|
||||
status,
|
||||
text
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn http_client() -> &'static reqwest::Client {
|
||||
static CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
|
||||
CLIENT.get_or_init(reqwest::Client::new)
|
||||
}
|
||||
@@ -399,8 +399,30 @@ pub async fn send_alert(
|
||||
AlertData::None {} => Default::default(),
|
||||
};
|
||||
if !text.is_empty() {
|
||||
let slack = ::slack::Client::new(url);
|
||||
slack.send_message(text, blocks).await?;
|
||||
let vars_and_secrets = get_variables_and_secrets().await?;
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
let mut url_interpolated = url.to_string();
|
||||
|
||||
// interpolate variables and secrets into the url
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut url_interpolated,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
let slack = ::slack::Client::new(url_interpolated);
|
||||
slack.send_message(text, blocks).await.map_err(|e| {
|
||||
let replacers =
|
||||
secret_replacers.into_iter().collect::<Vec<_>>();
|
||||
let sanitized_error =
|
||||
svi::replace_in_string(&format!("{e:?}"), &replacers);
|
||||
anyhow::Error::msg(format!(
|
||||
"Error with slack request: {}",
|
||||
sanitized_error
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -86,7 +86,13 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut vars_and_secrets = get_variables_and_secrets().await?;
|
||||
// Add the $VERSION to variables. Use with [[$VERSION]]
|
||||
vars_and_secrets.variables.insert(
|
||||
String::from("$VERSION"),
|
||||
build.config.version.to_string(),
|
||||
);
|
||||
|
||||
if build.config.builder_id.is_empty() {
|
||||
return Err(anyhow!("Must attach builder to RunBuild").into());
|
||||
@@ -110,14 +116,6 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
update.version = build.config.version;
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
// Add the $VERSION to variables. Use with [[$VERSION]]
|
||||
if !vars_and_secrets.variables.contains_key("$VERSION") {
|
||||
vars_and_secrets.variables.insert(
|
||||
String::from("$VERSION"),
|
||||
build.config.version.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let git_token = git_token(
|
||||
&build.config.git_provider,
|
||||
&build.config.git_account,
|
||||
@@ -177,7 +175,6 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
});
|
||||
|
||||
// GET BUILDER PERIPHERY
|
||||
|
||||
let (periphery, cleanup_data) = match get_builder_periphery(
|
||||
build.name.clone(),
|
||||
Some(build.config.version),
|
||||
@@ -203,9 +200,8 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
}
|
||||
};
|
||||
|
||||
// CLONE REPO
|
||||
// INTERPOLATE VARIABLES
|
||||
let secret_replacers = if !build.config.skip_secret_interp {
|
||||
// Interpolate variables / secrets into pre build command
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
|
||||
@@ -216,6 +212,34 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut build.config.build_args,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut build.config.secret_args,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut build.config.dockerfile,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_extra_args(
|
||||
&vars_and_secrets,
|
||||
&mut build.config.extra_args,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
add_interp_update_log(
|
||||
&mut update,
|
||||
&global_replacers,
|
||||
@@ -227,84 +251,57 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let res = tokio::select! {
|
||||
res = periphery
|
||||
.request(api::git::CloneRepo {
|
||||
args: (&build).into(),
|
||||
git_token,
|
||||
environment: Default::default(),
|
||||
env_file_path: Default::default(),
|
||||
skip_secret_interp: Default::default(),
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
}) => res,
|
||||
_ = cancel.cancelled() => {
|
||||
debug!("build cancelled during clone, cleaning up builder");
|
||||
update.push_error_log("build cancelled", String::from("user cancelled build during repo clone"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
info!("builder cleaned up");
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
},
|
||||
};
|
||||
|
||||
let commit_message = match res {
|
||||
Ok(res) => {
|
||||
debug!("finished repo clone");
|
||||
update.logs.extend(res.logs);
|
||||
update.commit_hash =
|
||||
res.commit_hash.unwrap_or_default().to_string();
|
||||
res.commit_message.unwrap_or_default()
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("failed build at clone repo | {e:#}");
|
||||
update.push_error_log(
|
||||
"clone repo",
|
||||
format_serror(&e.context("failed to clone repo").into()),
|
||||
);
|
||||
Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
if all_logs_success(&update.logs) {
|
||||
let secret_replacers = if !build.config.skip_secret_interp {
|
||||
// Interpolate variables / secrets into build args
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut build.config.build_args,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
&mut build.config.secret_args,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_extra_args(
|
||||
&vars_and_secrets,
|
||||
&mut build.config.extra_args,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
add_interp_update_log(
|
||||
&mut update,
|
||||
&global_replacers,
|
||||
&secret_replacers,
|
||||
);
|
||||
|
||||
secret_replacers
|
||||
} else {
|
||||
Default::default()
|
||||
let commit_message = if !build.config.files_on_host
|
||||
&& !build.config.repo.is_empty()
|
||||
{
|
||||
// CLONE REPO
|
||||
let res = tokio::select! {
|
||||
res = periphery
|
||||
.request(api::git::CloneRepo {
|
||||
args: (&build).into(),
|
||||
git_token,
|
||||
environment: Default::default(),
|
||||
env_file_path: Default::default(),
|
||||
skip_secret_interp: Default::default(),
|
||||
replacers: Default::default(),
|
||||
}) => res,
|
||||
_ = cancel.cancelled() => {
|
||||
debug!("build cancelled during clone, cleaning up builder");
|
||||
update.push_error_log("build cancelled", String::from("user cancelled build during repo clone"));
|
||||
cleanup_builder_instance(cleanup_data, &mut update)
|
||||
.await;
|
||||
info!("builder cleaned up");
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
},
|
||||
};
|
||||
|
||||
let commit_message = match res {
|
||||
Ok(res) => {
|
||||
debug!("finished repo clone");
|
||||
update.logs.extend(res.logs);
|
||||
update.commit_hash =
|
||||
res.commit_hash.unwrap_or_default().to_string();
|
||||
res.commit_message.unwrap_or_default()
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("failed build at clone repo | {e:#}");
|
||||
update.push_error_log(
|
||||
"clone repo",
|
||||
format_serror(&e.context("failed to clone repo").into()),
|
||||
);
|
||||
Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Some(commit_message)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if all_logs_success(&update.logs) {
|
||||
// RUN BUILD
|
||||
let res = tokio::select! {
|
||||
res = periphery
|
||||
.request(api::build::Build {
|
||||
@@ -321,7 +318,7 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
_ = cancel.cancelled() => {
|
||||
info!("build cancelled during build, cleaning up builder");
|
||||
update.push_error_log("build cancelled", String::from("user cancelled build during docker build"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
cleanup_builder_instance(cleanup_data, &mut update)
|
||||
.await;
|
||||
return handle_early_return(update, build.id, build.name, true).await
|
||||
},
|
||||
@@ -365,8 +362,9 @@ impl Resolve<ExecuteArgs> for RunBuild {
|
||||
// stop the cancel listening task from going forever
|
||||
cancel.cancel();
|
||||
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
// If building on temporary cloud server (AWS),
|
||||
// this will terminate the server.
|
||||
cleanup_builder_instance(cleanup_data, &mut update).await;
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
|
||||
@@ -462,7 +462,7 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
_ = cancel.cancelled() => {
|
||||
debug!("build cancelled during clone, cleaning up builder");
|
||||
update.push_error_log("build cancelled", String::from("user cancelled build during repo clone"));
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
cleanup_builder_instance(cleanup_data, &mut update)
|
||||
.await;
|
||||
info!("builder cleaned up");
|
||||
return handle_builder_early_return(update, repo.id, repo.name, true).await
|
||||
@@ -506,8 +506,9 @@ impl Resolve<ExecuteArgs> for BuildRepo {
|
||||
// stop the cancel listening task from going forever
|
||||
cancel.cancel();
|
||||
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
.await;
|
||||
// If building on temporary cloud server (AWS),
|
||||
// this will terminate the server.
|
||||
cleanup_builder_instance(cleanup_data, &mut update).await;
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with add_update.
|
||||
|
||||
@@ -116,6 +116,7 @@ enum ReadRequest {
|
||||
InspectDockerImage(InspectDockerImage),
|
||||
ListDockerImageHistory(ListDockerImageHistory),
|
||||
InspectDockerVolume(InspectDockerVolume),
|
||||
GetDockerContainersSummary(GetDockerContainersSummary),
|
||||
ListAllDockerContainers(ListAllDockerContainers),
|
||||
ListDockerContainers(ListDockerContainers),
|
||||
ListDockerNetworks(ListDockerNetworks),
|
||||
|
||||
@@ -14,7 +14,9 @@ use komodo_client::{
|
||||
ResourceTarget,
|
||||
deployment::Deployment,
|
||||
docker::{
|
||||
container::{Container, ContainerListItem},
|
||||
container::{
|
||||
Container, ContainerListItem, ContainerStateStatusEnum,
|
||||
},
|
||||
image::{Image, ImageHistoryResponseItem},
|
||||
network::Network,
|
||||
volume::Volume,
|
||||
@@ -413,6 +415,45 @@ impl Resolve<ReadArgs> for ListAllDockerContainers {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for GetDockerContainersSummary {
|
||||
async fn resolve(
|
||||
self,
|
||||
ReadArgs { user }: &ReadArgs,
|
||||
) -> serror::Result<GetDockerContainersSummaryResponse> {
|
||||
let servers = resource::list_full_for_user::<Server>(
|
||||
Default::default(),
|
||||
user,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to get servers from db")?;
|
||||
|
||||
let mut res = GetDockerContainersSummaryResponse::default();
|
||||
|
||||
for server in servers {
|
||||
let cache = server_status_cache()
|
||||
.get_or_insert_default(&server.id)
|
||||
.await;
|
||||
|
||||
if let Some(containers) = &cache.containers {
|
||||
for container in containers {
|
||||
res.total += 1;
|
||||
match container.state {
|
||||
ContainerStateStatusEnum::Created
|
||||
| ContainerStateStatusEnum::Paused
|
||||
| ContainerStateStatusEnum::Exited => res.stopped += 1,
|
||||
ContainerStateStatusEnum::Running => res.running += 1,
|
||||
ContainerStateStatusEnum::Empty => res.unknown += 1,
|
||||
_ => res.unhealthy += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ReadArgs> for InspectDockerContainer {
|
||||
async fn resolve(
|
||||
self,
|
||||
|
||||
+339
-37
@@ -1,12 +1,17 @@
|
||||
use std::{path::PathBuf, str::FromStr, time::Duration};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use formatting::format_serror;
|
||||
use git::GitRes;
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
CloneArgs, NoData,
|
||||
CloneArgs, FileContents, NoData, Operation, all_logs_success,
|
||||
build::{Build, BuildInfo, PartialBuildConfig},
|
||||
builder::{Builder, BuilderConfig},
|
||||
config::core::CoreConfig,
|
||||
permission::PermissionLevel,
|
||||
server::ServerState,
|
||||
update::Update,
|
||||
},
|
||||
};
|
||||
@@ -15,11 +20,22 @@ use mungos::mongodb::bson::to_document;
|
||||
use octorust::types::{
|
||||
ReposCreateWebhookRequest, ReposCreateWebhookRequestConfig,
|
||||
};
|
||||
use periphery_client::{
|
||||
PeripheryClient,
|
||||
api::build::{
|
||||
GetDockerfileContentsOnHost, WriteDockerfileContentsToHost,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::git_token,
|
||||
helpers::{
|
||||
git_token, periphery_client,
|
||||
query::get_server_with_state,
|
||||
update::{add_update, make_update},
|
||||
},
|
||||
resource,
|
||||
state::{db_client, github_client},
|
||||
};
|
||||
@@ -88,6 +104,184 @@ impl Resolve<WriteArgs> for RenameBuild {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for WriteBuildFileContents {
|
||||
#[instrument(name = "WriteBuildFileContents", skip(args))]
|
||||
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
|
||||
let build = resource::get_check_permissions::<Build>(
|
||||
&self.build,
|
||||
&args.user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !build.config.files_on_host && build.config.repo.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Build is not configured to use Files on Host or Git Repo, can't write dockerfile contents"
|
||||
).into());
|
||||
}
|
||||
|
||||
let mut update =
|
||||
make_update(&build, Operation::WriteDockerfile, &args.user);
|
||||
|
||||
update.push_simple_log("Dockerfile to write", &self.contents);
|
||||
|
||||
if build.config.files_on_host {
|
||||
match get_on_host_periphery(&build)
|
||||
.await?
|
||||
.request(WriteDockerfileContentsToHost {
|
||||
name: build.name,
|
||||
build_path: build.config.build_path,
|
||||
dockerfile_path: build.config.dockerfile_path,
|
||||
contents: self.contents,
|
||||
})
|
||||
.await
|
||||
.context("Failed to write dockerfile contents to host")
|
||||
{
|
||||
Ok(log) => {
|
||||
update.logs.push(log);
|
||||
}
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"Write Dockerfile Contents",
|
||||
format_serror(&e.into()),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if !all_logs_success(&update.logs) {
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
(RefreshBuildCache { build: build.id }).resolve(args).await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh build cache",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
} else {
|
||||
write_dockerfile_contents_git(self, args, build, update).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_dockerfile_contents_git(
|
||||
req: WriteBuildFileContents,
|
||||
args: &WriteArgs,
|
||||
build: Build,
|
||||
mut update: Update,
|
||||
) -> serror::Result<Update> {
|
||||
let WriteBuildFileContents { build: _, contents } = req;
|
||||
|
||||
let mut clone_args: CloneArgs = (&build).into();
|
||||
let root = clone_args.unique_path(&core_config().repo_directory)?;
|
||||
|
||||
let build_path = build
|
||||
.config
|
||||
.build_path
|
||||
.parse::<PathBuf>()
|
||||
.context("Invalid build path")?;
|
||||
let dockerfile_path = build
|
||||
.config
|
||||
.dockerfile_path
|
||||
.parse::<PathBuf>()
|
||||
.context("Invalid dockerfile path")?;
|
||||
|
||||
let full_path = root.join(&build_path).join(&dockerfile_path);
|
||||
|
||||
if let Some(parent) = full_path.parent() {
|
||||
fs::create_dir_all(parent).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to initialize dockerfile 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 dockerfile contents to {full_path:?}")
|
||||
})
|
||||
{
|
||||
update
|
||||
.push_error_log("Write Dockerfile", format_serror(&e.into()));
|
||||
} else {
|
||||
update.push_simple_log(
|
||||
"Write Dockerfile",
|
||||
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 Dockerfile", args.user.username),
|
||||
&root,
|
||||
&build_path.join(&dockerfile_path),
|
||||
&build.config.branch,
|
||||
)
|
||||
.await;
|
||||
|
||||
update.logs.extend(commit_res.logs);
|
||||
|
||||
if let Err(e) = (RefreshBuildCache { build: build.name })
|
||||
.resolve(args)
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh build cache",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for RefreshBuildCache {
|
||||
#[instrument(
|
||||
name = "RefreshBuildCache",
|
||||
@@ -107,55 +301,104 @@ impl Resolve<WriteArgs> for RefreshBuildCache {
|
||||
)
|
||||
.await?;
|
||||
|
||||
if build.config.repo.is_empty()
|
||||
|| build.config.git_provider.is_empty()
|
||||
{
|
||||
// Nothing to do here
|
||||
return Ok(NoData {});
|
||||
}
|
||||
let (
|
||||
remote_path,
|
||||
remote_contents,
|
||||
remote_error,
|
||||
latest_hash,
|
||||
latest_message,
|
||||
) = if build.config.files_on_host {
|
||||
// =============
|
||||
// FILES ON HOST
|
||||
// =============
|
||||
match get_on_host_dockerfile(&build).await {
|
||||
Ok(FileContents { path, contents }) => {
|
||||
(Some(path), Some(contents), None, None, None)
|
||||
}
|
||||
Err(e) => {
|
||||
(None, None, Some(format_serror(&e.into())), None, None)
|
||||
}
|
||||
}
|
||||
} else if !build.config.repo.is_empty() {
|
||||
// ================
|
||||
// REPO BASED BUILD
|
||||
// ================
|
||||
if build.config.git_provider.is_empty() {
|
||||
// Nothing to do here
|
||||
return Ok(NoData {});
|
||||
}
|
||||
let config = core_config();
|
||||
|
||||
let config = core_config();
|
||||
let mut clone_args: CloneArgs = (&build).into();
|
||||
let repo_path =
|
||||
clone_args.unique_path(&core_config().repo_directory)?;
|
||||
clone_args.destination = Some(repo_path.display().to_string());
|
||||
// Don't want to run these on core.
|
||||
clone_args.on_clone = None;
|
||||
clone_args.on_pull = None;
|
||||
|
||||
let mut clone_args: CloneArgs = (&build).into();
|
||||
let repo_path =
|
||||
clone_args.unique_path(&core_config().repo_directory)?;
|
||||
clone_args.destination = Some(repo_path.display().to_string());
|
||||
// Don't want to run these on core.
|
||||
clone_args.on_clone = None;
|
||||
clone_args.on_pull = None;
|
||||
|
||||
let access_token = if let Some(username) = &clone_args.account {
|
||||
git_token(&clone_args.provider, username, |https| {
|
||||
let access_token = if let Some(username) = &clone_args.account {
|
||||
git_token(&clone_args.provider, username, |https| {
|
||||
clone_args.https = https
|
||||
})
|
||||
.await
|
||||
.with_context(
|
||||
|| format!("Failed to get git token in call to db. Stopping run. | {} | {username}", clone_args.provider),
|
||||
)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let GitRes {
|
||||
hash: latest_hash,
|
||||
message: latest_message,
|
||||
..
|
||||
} = git::pull_or_clone(
|
||||
clone_args,
|
||||
&config.repo_directory,
|
||||
access_token,
|
||||
&[],
|
||||
"",
|
||||
None,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to clone build repo")?;
|
||||
let GitRes { hash, message, .. } = git::pull_or_clone(
|
||||
clone_args,
|
||||
&config.repo_directory,
|
||||
access_token,
|
||||
&[],
|
||||
"",
|
||||
None,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to clone build repo")?;
|
||||
|
||||
let relative_path = PathBuf::from_str(&build.config.build_path)
|
||||
.context("Invalid build path")?
|
||||
.join(&build.config.dockerfile_path);
|
||||
|
||||
let full_path = repo_path.join(&relative_path);
|
||||
let (contents, error) = match fs::read_to_string(&full_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to read dockerfile contents at {full_path:?}"
|
||||
)
|
||||
}) {
|
||||
Ok(contents) => (Some(contents), None),
|
||||
Err(e) => (None, Some(format_serror(&e.into()))),
|
||||
};
|
||||
|
||||
(
|
||||
Some(relative_path.display().to_string()),
|
||||
contents,
|
||||
error,
|
||||
hash,
|
||||
message,
|
||||
)
|
||||
} else {
|
||||
// =============
|
||||
// UI BASED FILE
|
||||
// =============
|
||||
(None, None, None, None, None)
|
||||
};
|
||||
|
||||
let info = BuildInfo {
|
||||
last_built_at: build.info.last_built_at,
|
||||
built_hash: build.info.built_hash,
|
||||
built_message: build.info.built_message,
|
||||
built_contents: build.info.built_contents,
|
||||
remote_path,
|
||||
remote_contents,
|
||||
remote_error,
|
||||
latest_hash,
|
||||
latest_message,
|
||||
};
|
||||
@@ -176,6 +419,65 @@ impl Resolve<WriteArgs> for RefreshBuildCache {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_on_host_periphery(
|
||||
build: &Build,
|
||||
) -> anyhow::Result<PeripheryClient> {
|
||||
if build.config.builder_id.is_empty() {
|
||||
return Err(anyhow!("No builder associated with build"));
|
||||
}
|
||||
|
||||
let builder = resource::get::<Builder>(&build.config.builder_id)
|
||||
.await
|
||||
.context("Failed to get builder")?;
|
||||
|
||||
match builder.config {
|
||||
BuilderConfig::Aws(_) => {
|
||||
return Err(anyhow!(
|
||||
"Files on host doesn't work with AWS builder"
|
||||
));
|
||||
}
|
||||
BuilderConfig::Url(config) => {
|
||||
let periphery = PeripheryClient::new(
|
||||
config.address,
|
||||
config.passkey,
|
||||
Duration::from_secs(3),
|
||||
);
|
||||
periphery.health_check().await?;
|
||||
Ok(periphery)
|
||||
}
|
||||
BuilderConfig::Server(config) => {
|
||||
if config.server_id.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Builder is type server, but has no server attached"
|
||||
));
|
||||
}
|
||||
let (server, state) =
|
||||
get_server_with_state(&config.server_id).await?;
|
||||
if state != ServerState::Ok {
|
||||
return Err(anyhow!(
|
||||
"Builder server is disabled or not reachable"
|
||||
));
|
||||
};
|
||||
periphery_client(&server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The successful case will be included as Some(remote_contents).
|
||||
/// The error case will be included as Some(remote_error)
|
||||
async fn get_on_host_dockerfile(
|
||||
build: &Build,
|
||||
) -> anyhow::Result<FileContents> {
|
||||
get_on_host_periphery(build)
|
||||
.await?
|
||||
.request(GetDockerfileContentsOnHost {
|
||||
name: build.name.clone(),
|
||||
build_path: build.config.build_path.clone(),
|
||||
dockerfile_path: build.config.dockerfile_path.clone(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for CreateBuildWebhook {
|
||||
#[instrument(name = "CreateBuildWebhook", skip(args))]
|
||||
async fn resolve(
|
||||
|
||||
@@ -96,6 +96,7 @@ pub enum WriteRequest {
|
||||
DeleteBuild(DeleteBuild),
|
||||
UpdateBuild(UpdateBuild),
|
||||
RenameBuild(RenameBuild),
|
||||
WriteBuildFileContents(WriteBuildFileContents),
|
||||
RefreshBuildCache(RefreshBuildCache),
|
||||
CreateBuildWebhook(CreateBuildWebhook),
|
||||
DeleteBuildWebhook(DeleteBuildWebhook),
|
||||
|
||||
@@ -211,7 +211,7 @@ impl Resolve<WriteArgs> for WriteStackFileContents {
|
||||
}
|
||||
|
||||
update.finalize();
|
||||
add_update(update.clone()).await?;
|
||||
update.id = add_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
@@ -132,6 +132,38 @@ impl Resolve<WriteArgs> for RenameResourceSync {
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for WriteSyncFileContents {
|
||||
#[instrument(name = "WriteSyncFileContents", skip(args))]
|
||||
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&self.sync,
|
||||
&args.user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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."
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut update =
|
||||
make_update(&sync, Operation::WriteSyncContents, &args.user);
|
||||
|
||||
update.push_simple_log("File contents", &self.contents);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_sync_file_contents_on_host(
|
||||
req: WriteSyncFileContents,
|
||||
args: &WriteArgs,
|
||||
@@ -298,7 +330,7 @@ async fn write_sync_file_contents_git(
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh failed",
|
||||
"Refresh sync pending",
|
||||
format_serror(&e.error.into()),
|
||||
);
|
||||
}
|
||||
@@ -309,37 +341,6 @@ async fn write_sync_file_contents_git(
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for WriteSyncFileContents {
|
||||
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
|
||||
let sync = resource::get_check_permissions::<ResourceSync>(
|
||||
&self.sync,
|
||||
&args.user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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."
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut update =
|
||||
make_update(&sync, Operation::WriteSyncContents, &args.user);
|
||||
|
||||
update.push_simple_log("File contents", &self.contents);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<WriteArgs> for CommitSync {
|
||||
#[instrument(name = "CommitSync", skip(args))]
|
||||
async fn resolve(self, args: &WriteArgs) -> serror::Result<Update> {
|
||||
|
||||
@@ -5,6 +5,8 @@ pub mod hetzner;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BuildCleanupData {
|
||||
Server { repo_name: String },
|
||||
/// Nothing to clean up
|
||||
Server,
|
||||
/// Clean up AWS instance
|
||||
Aws { instance_id: String, region: String },
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
title: env.komodo_title.unwrap_or(config.title),
|
||||
host: env.komodo_host.unwrap_or(config.host),
|
||||
port: env.komodo_port.unwrap_or(config.port),
|
||||
bind_ip: env.komodo_bind_ip.unwrap_or(config.bind_ip),
|
||||
first_server: env.komodo_first_server.unwrap_or(config.first_server),
|
||||
frontend_path: env.komodo_frontend_path.unwrap_or(config.frontend_path),
|
||||
jwt_ttl: env
|
||||
|
||||
@@ -61,12 +61,7 @@ pub async fn get_builder_periphery(
|
||||
.health_check()
|
||||
.await
|
||||
.context("Url Builder failed health check")?;
|
||||
Ok((
|
||||
periphery,
|
||||
BuildCleanupData::Server {
|
||||
repo_name: resource_name,
|
||||
},
|
||||
))
|
||||
Ok((periphery, BuildCleanupData::Server))
|
||||
}
|
||||
BuilderConfig::Server(config) => {
|
||||
if config.server_id.is_empty() {
|
||||
@@ -74,12 +69,7 @@ pub async fn get_builder_periphery(
|
||||
}
|
||||
let server = resource::get::<Server>(&config.server_id).await?;
|
||||
let periphery = periphery_client(&server)?;
|
||||
Ok((
|
||||
periphery,
|
||||
BuildCleanupData::Server {
|
||||
repo_name: resource_name,
|
||||
},
|
||||
))
|
||||
Ok((periphery, BuildCleanupData::Server))
|
||||
}
|
||||
BuilderConfig::Aws(config) => {
|
||||
get_aws_builder(&resource_name, version, config, update).await
|
||||
@@ -179,17 +169,14 @@ async fn get_aws_builder(
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip(periphery, update))]
|
||||
#[instrument(skip(update))]
|
||||
pub async fn cleanup_builder_instance(
|
||||
periphery: PeripheryClient,
|
||||
cleanup_data: BuildCleanupData,
|
||||
update: &mut Update,
|
||||
) {
|
||||
match cleanup_data {
|
||||
BuildCleanupData::Server { repo_name } => {
|
||||
let _ = periphery
|
||||
.request(api::git::DeleteRepo { name: repo_name })
|
||||
.await;
|
||||
BuildCleanupData::Server => {
|
||||
// Nothing to clean up
|
||||
}
|
||||
BuildCleanupData::Aws {
|
||||
instance_id,
|
||||
|
||||
@@ -78,6 +78,9 @@ pub async fn git_token(
|
||||
account_username: &str,
|
||||
mut on_https_found: impl FnMut(bool),
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
if provider_domain.is_empty() || account_username.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let db_provider = db_client()
|
||||
.git_accounts
|
||||
.find_one(doc! { "domain": provider_domain, "username": account_username })
|
||||
@@ -143,7 +146,11 @@ pub fn periphery_client(
|
||||
|
||||
let client = PeripheryClient::new(
|
||||
&server.config.address,
|
||||
&core_config().passkey,
|
||||
if server.config.passkey.is_empty() {
|
||||
&core_config().passkey
|
||||
} else {
|
||||
&server.config.passkey
|
||||
},
|
||||
Duration::from_secs(server.config.timeout_seconds as u64),
|
||||
);
|
||||
|
||||
@@ -352,4 +359,4 @@ pub async fn ensure_first_server_and_builder() {
|
||||
e.error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,9 +86,10 @@ async fn app() -> anyhow::Result<()> {
|
||||
)
|
||||
.into_make_service();
|
||||
|
||||
let addr = format!("{}:{}", core_config().bind_ip, core_config().port);
|
||||
let socket_addr =
|
||||
SocketAddr::from_str(&format!("0.0.0.0:{}", core_config().port))
|
||||
.context("failed to parse socket addr")?;
|
||||
SocketAddr::from_str(&addr)
|
||||
.context("failed to parse listen address")?;
|
||||
|
||||
if config.ssl_enabled {
|
||||
info!("🔒 Core SSL Enabled");
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use komodo_client::entities::{
|
||||
Operation, ResourceTargetVariant,
|
||||
build::{
|
||||
Build, BuildConfig, BuildConfigDiff, BuildInfo, BuildListItem,
|
||||
BuildListItemInfo, BuildQuerySpecifics, BuildState,
|
||||
PartialBuildConfig,
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
api::write::RefreshBuildCache,
|
||||
entities::{
|
||||
Operation, ResourceTargetVariant,
|
||||
build::{
|
||||
Build, BuildConfig, BuildConfigDiff, BuildInfo, BuildListItem,
|
||||
BuildListItemInfo, BuildQuerySpecifics, BuildState,
|
||||
PartialBuildConfig,
|
||||
},
|
||||
builder::Builder,
|
||||
environment_vars_from_str, optional_string,
|
||||
permission::PermissionLevel,
|
||||
resource::Resource,
|
||||
update::Update,
|
||||
user::{User, build_user},
|
||||
},
|
||||
builder::Builder,
|
||||
environment_vars_from_str,
|
||||
permission::PermissionLevel,
|
||||
resource::Resource,
|
||||
update::Update,
|
||||
user::User,
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{Collection, bson::doc, options::FindOptions},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::write::WriteArgs,
|
||||
config::core_config,
|
||||
helpers::{empty_or_only_spaces, query::get_latest_update},
|
||||
state::{action_states, build_state_cache, db_client},
|
||||
@@ -56,10 +62,13 @@ impl super::KomodoResource for Build {
|
||||
last_built_at: build.info.last_built_at,
|
||||
version: build.config.version,
|
||||
builder_id: build.config.builder_id,
|
||||
git_provider: build.config.git_provider,
|
||||
image_registry_domain: build.config.image_registry.domain,
|
||||
repo: build.config.repo,
|
||||
branch: build.config.branch,
|
||||
files_on_host: build.config.files_on_host,
|
||||
git_provider: optional_string(build.config.git_provider),
|
||||
repo: optional_string(build.config.repo),
|
||||
branch: optional_string(build.config.branch),
|
||||
image_registry_domain: optional_string(
|
||||
build.config.image_registry.domain,
|
||||
),
|
||||
built_hash: build.info.built_hash,
|
||||
latest_hash: build.info.latest_hash,
|
||||
state,
|
||||
@@ -96,10 +105,23 @@ impl super::KomodoResource for Build {
|
||||
}
|
||||
|
||||
async fn post_create(
|
||||
_created: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
created: &Resource<Self::Config, Self::Info>,
|
||||
update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
refresh_build_state_cache().await;
|
||||
if let Err(e) = (RefreshBuildCache {
|
||||
build: created.name.clone(),
|
||||
})
|
||||
.resolve(&WriteArgs {
|
||||
user: build_user().to_owned(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh build cache",
|
||||
format_serror(&e.error.context("The build cache has failed to refresh. This may be due to a misconfiguration of the Build").into())
|
||||
);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@ impl super::KomodoResource for Repo {
|
||||
} else {
|
||||
repo.config.path.clone()
|
||||
},
|
||||
is_build: false,
|
||||
})
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -162,7 +162,7 @@ impl super::KomodoResource for Stack {
|
||||
{
|
||||
update.push_error_log(
|
||||
"Refresh stack cache",
|
||||
format_serror(&e.error.context("The stack cache has failed to refresh. This is likely due to a misconfiguration of the Stack").into())
|
||||
format_serror(&e.error.context("The stack cache has failed to refresh. This may be due to a misconfiguration of the Stack").into())
|
||||
);
|
||||
};
|
||||
if created.config.server_id.is_empty() {
|
||||
|
||||
@@ -26,7 +26,7 @@ pub async fn get_repo_compose_contents(
|
||||
let (repo_path, _logs, hash, message) =
|
||||
ensure_remote_repo(clone_args)
|
||||
.await
|
||||
.context("failed to clone stack repo")?;
|
||||
.context("Failed to clone stack repo")?;
|
||||
|
||||
let run_directory = repo_path.join(&stack.config.run_directory);
|
||||
// This will remove any intermediate '/./' which can be a problem for some OS.
|
||||
@@ -44,7 +44,7 @@ pub async fn get_repo_compose_contents(
|
||||
}
|
||||
// If file does not exist, will show up in err case so the log is handled
|
||||
match fs::read_to_string(&file_path).with_context(|| {
|
||||
format!("failed to read file contents from {file_path:?}")
|
||||
format!("Failed to read file contents from {file_path:?}")
|
||||
}) {
|
||||
Ok(contents) => successful.push(FileContents {
|
||||
path: path.to_string(),
|
||||
@@ -102,6 +102,6 @@ pub async fn ensure_remote_repo(
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to clone stack repo")
|
||||
.context("Failed to clone stack repo")
|
||||
.map(|res| (repo_path, res.logs, res.hash, res.message))
|
||||
}
|
||||
|
||||
@@ -8,18 +8,15 @@ pub fn extract_services_from_stack(
|
||||
stack: &Stack,
|
||||
) -> Vec<StackServiceNames> {
|
||||
if let Some(mut services) = stack.info.deployed_services.clone() {
|
||||
if services.iter().any(|service| service.image.is_empty()) {
|
||||
for service in
|
||||
services.iter_mut().filter(|s| s.image.is_empty())
|
||||
{
|
||||
service.image = stack
|
||||
.info
|
||||
.latest_services
|
||||
.iter()
|
||||
.find(|s| s.service_name == service.service_name)
|
||||
.map(|s| s.image.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
for service in services.iter_mut().filter(|s| s.image.is_empty())
|
||||
{
|
||||
service.image = stack
|
||||
.info
|
||||
.latest_services
|
||||
.iter()
|
||||
.find(|s| s.service_name == service.service_name)
|
||||
.map(|s| s.image.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
services
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
FROM rust:1.85.0-bullseye AS builder
|
||||
FROM rust:1.85.1-bullseye AS builder
|
||||
|
||||
WORKDIR /builder
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
+178
-14
@@ -1,8 +1,12 @@
|
||||
use std::{fmt::Write, path::Path};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::{
|
||||
run_komodo_command, run_komodo_command_with_interpolation,
|
||||
run_komodo_command, run_komodo_command_multiline,
|
||||
run_komodo_command_with_interpolation,
|
||||
};
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
@@ -16,9 +20,12 @@ use komodo_client::{
|
||||
parsers::QUOTE_PATTERN,
|
||||
};
|
||||
use periphery_client::api::build::{
|
||||
self, PruneBuilders, PruneBuildx,
|
||||
self, GetDockerfileContentsOnHost,
|
||||
GetDockerfileContentsOnHostResponse, PruneBuilders, PruneBuildx,
|
||||
WriteDockerfileContentsToHost,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
@@ -26,6 +33,88 @@ use crate::{
|
||||
helpers::{parse_extra_args, parse_labels},
|
||||
};
|
||||
|
||||
impl Resolve<super::Args> for GetDockerfileContentsOnHost {
|
||||
#[instrument(name = "GetDockerfileContentsOnHost", level = "debug")]
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
) -> serror::Result<GetDockerfileContentsOnHostResponse> {
|
||||
let GetDockerfileContentsOnHost {
|
||||
name,
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
} = self;
|
||||
|
||||
let root =
|
||||
periphery_config().build_dir.join(to_komodo_name(&name));
|
||||
let build_dir =
|
||||
root.join(&build_path).components().collect::<PathBuf>();
|
||||
|
||||
if !build_dir.exists() {
|
||||
fs::create_dir_all(&build_dir)
|
||||
.await
|
||||
.context("Failed to initialize build directory")?;
|
||||
}
|
||||
|
||||
let full_path = build_dir
|
||||
.join(&dockerfile_path)
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
|
||||
let contents =
|
||||
fs::read_to_string(&full_path).await.with_context(|| {
|
||||
format!("Failed to read dockerfile contents at {full_path:?}")
|
||||
})?;
|
||||
|
||||
Ok(GetDockerfileContentsOnHostResponse {
|
||||
contents,
|
||||
path: full_path.display().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<super::Args> for WriteDockerfileContentsToHost {
|
||||
#[instrument(
|
||||
name = "WriteDockerfileContentsToHost",
|
||||
skip_all,
|
||||
fields(
|
||||
stack = &self.name,
|
||||
build_path = &self.build_path,
|
||||
dockerfile_path = &self.dockerfile_path,
|
||||
)
|
||||
)]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let WriteDockerfileContentsToHost {
|
||||
name,
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
contents,
|
||||
} = self;
|
||||
let full_path = periphery_config()
|
||||
.build_dir
|
||||
.join(to_komodo_name(&name))
|
||||
.join(&build_path)
|
||||
.join(dockerfile_path)
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = full_path.parent() {
|
||||
if !parent.exists() {
|
||||
tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize dockerfile parent directory {parent:?}"))?;
|
||||
}
|
||||
}
|
||||
fs::write(&full_path, contents).await.with_context(|| {
|
||||
format!("Failed to write dockerfile contents to {full_path:?}")
|
||||
})?;
|
||||
Ok(Log::simple(
|
||||
"Write dockerfile to host",
|
||||
format!("dockerfile contents written to {full_path:?}"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<super::Args> for build::Build {
|
||||
#[instrument(name = "Build", skip_all, fields(build = self.build.name.to_string()))]
|
||||
async fn resolve(
|
||||
@@ -36,7 +125,7 @@ impl Resolve<super::Args> for build::Build {
|
||||
build,
|
||||
registry_token,
|
||||
additional_tags,
|
||||
replacers: core_replacers,
|
||||
replacers: mut core_replacers,
|
||||
} = self;
|
||||
let Build {
|
||||
name,
|
||||
@@ -53,11 +142,19 @@ impl Resolve<super::Args> for build::Build {
|
||||
extra_args,
|
||||
use_buildx,
|
||||
image_registry,
|
||||
repo,
|
||||
files_on_host,
|
||||
dockerfile,
|
||||
pre_build,
|
||||
..
|
||||
},
|
||||
..
|
||||
} = &build;
|
||||
|
||||
if !*files_on_host && repo.is_empty() && dockerfile.is_empty() {
|
||||
return Err(anyhow!("Build must be files on host mode, have a repo attached, or have dockerfile contents set to build").into());
|
||||
}
|
||||
|
||||
let mut logs = Vec::new();
|
||||
|
||||
// Maybe docker login
|
||||
@@ -82,14 +179,81 @@ impl Resolve<super::Args> for build::Build {
|
||||
|
||||
let name = to_komodo_name(name);
|
||||
|
||||
// Get paths
|
||||
let build_dir =
|
||||
periphery_config().repo_dir.join(&name).join(build_path);
|
||||
let dockerfile_path = match optional_string(dockerfile_path) {
|
||||
Some(dockerfile_path) => dockerfile_path.to_owned(),
|
||||
None => "Dockerfile".to_owned(),
|
||||
let build_path =
|
||||
periphery_config().build_dir.join(&name).join(build_path);
|
||||
let dockerfile_path = optional_string(dockerfile_path)
|
||||
.unwrap_or("Dockerfile".to_owned());
|
||||
|
||||
// Write UI defined Dockerfile to host
|
||||
if !*files_on_host && repo.is_empty() && !dockerfile.is_empty() {
|
||||
let dockerfile = if *skip_secret_interp {
|
||||
dockerfile.to_string()
|
||||
} else {
|
||||
let (dockerfile, replacers) = svi::interpolate_variables(
|
||||
dockerfile,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
).context("Failed to interpolate variables into UI defined dockerfile")?;
|
||||
core_replacers.extend(replacers);
|
||||
dockerfile
|
||||
};
|
||||
|
||||
let full_path = build_path
|
||||
.join(&dockerfile_path)
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = full_path.parent() {
|
||||
if !parent.exists() {
|
||||
tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize dockerfile parent directory {parent:?}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
fs::write(&full_path, dockerfile).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to write dockerfile contents to {full_path:?}"
|
||||
)
|
||||
})?;
|
||||
|
||||
logs.push(Log::simple(
|
||||
"Write Dockerfile",
|
||||
format!("Dockerfile contents written to {full_path:?}"),
|
||||
));
|
||||
};
|
||||
|
||||
// Pre Build
|
||||
if !pre_build.is_none() {
|
||||
let pre_build_path = build_path.join(&pre_build.path);
|
||||
if let Some(log) = if !skip_secret_interp {
|
||||
run_komodo_command_with_interpolation(
|
||||
"Pre Build",
|
||||
Some(pre_build_path.as_path()),
|
||||
&pre_build.command,
|
||||
true,
|
||||
&periphery_config().secrets,
|
||||
&core_replacers,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
run_komodo_command_multiline(
|
||||
"Pre Build",
|
||||
Some(pre_build_path.as_path()),
|
||||
&pre_build.command,
|
||||
)
|
||||
.await
|
||||
} {
|
||||
let success = log.success;
|
||||
logs.push(log);
|
||||
if success {
|
||||
return Ok(logs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get command parts
|
||||
let image_name =
|
||||
get_image_name(&build).context("failed to make image name")?;
|
||||
@@ -109,7 +273,7 @@ impl Resolve<super::Args> for build::Build {
|
||||
.context("Invalid secret_args")?;
|
||||
let command_secret_args = parse_secret_args(
|
||||
&secret_args,
|
||||
&build_dir,
|
||||
&build_path,
|
||||
*skip_secret_interp,
|
||||
)
|
||||
.await?;
|
||||
@@ -131,14 +295,14 @@ impl Resolve<super::Args> for build::Build {
|
||||
if *skip_secret_interp {
|
||||
let build_log = run_komodo_command(
|
||||
"Docker Build",
|
||||
build_dir.as_ref(),
|
||||
build_path.as_ref(),
|
||||
command,
|
||||
)
|
||||
.await;
|
||||
logs.push(build_log);
|
||||
} else if let Some(log) = run_komodo_command_with_interpolation(
|
||||
"Docker Build",
|
||||
build_dir.as_ref(),
|
||||
build_path.as_ref(),
|
||||
command,
|
||||
false,
|
||||
&periphery_config().secrets,
|
||||
@@ -192,7 +356,7 @@ fn parse_build_args(build_args: &[EnvironmentVar]) -> String {
|
||||
.join("")
|
||||
}
|
||||
|
||||
/// https://docs.docker.com/build/building/secrets/#using-build-secrets
|
||||
/// <https://docs.docker.com/build/building/secrets/#using-build-secrets>
|
||||
async fn parse_secret_args(
|
||||
secret_args: &[EnvironmentVar],
|
||||
build_dir: &Path,
|
||||
|
||||
@@ -206,7 +206,7 @@ impl Resolve<super::Args> for WriteComposeContentsToHost {
|
||||
if let Some(parent) = file_path.parent() {
|
||||
fs::create_dir_all(&parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize environment file parent directory {parent:?}"))?;
|
||||
.with_context(|| format!("Failed to initialize compose file parent directory {parent:?}"))?;
|
||||
}
|
||||
fs::write(&file_path, contents).await.with_context(|| {
|
||||
format!(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{Context, anyhow};
|
||||
use formatting::format_serror;
|
||||
use git::GitRes;
|
||||
use komodo_client::entities::{CloneArgs, LatestCommit, update::Log};
|
||||
use periphery_client::api::git::{
|
||||
@@ -12,7 +13,7 @@ use tokio::fs;
|
||||
use crate::config::periphery_config;
|
||||
|
||||
impl Resolve<super::Args> for GetLatestCommit {
|
||||
#[instrument(name = "CloneRepo", level = "debug")]
|
||||
#[instrument(name = "GetLatestCommit", level = "debug")]
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &super::Args,
|
||||
@@ -68,9 +69,14 @@ impl Resolve<super::Args> for CloneRepo {
|
||||
)?,
|
||||
),
|
||||
};
|
||||
let parent_dir = if args.is_build {
|
||||
&periphery_config().build_dir
|
||||
} else {
|
||||
&periphery_config().repo_dir
|
||||
};
|
||||
git::clone(
|
||||
args,
|
||||
&periphery_config().repo_dir,
|
||||
parent_dir,
|
||||
token,
|
||||
&environment,
|
||||
&env_file_path,
|
||||
@@ -133,9 +139,14 @@ impl Resolve<super::Args> for PullRepo {
|
||||
)?,
|
||||
),
|
||||
};
|
||||
let parent_dir = if args.is_build {
|
||||
&periphery_config().build_dir
|
||||
} else {
|
||||
&periphery_config().repo_dir
|
||||
};
|
||||
git::pull(
|
||||
args,
|
||||
&periphery_config().repo_dir,
|
||||
parent_dir,
|
||||
token,
|
||||
&environment,
|
||||
&env_file_path,
|
||||
@@ -198,9 +209,14 @@ impl Resolve<super::Args> for PullOrCloneRepo {
|
||||
)?,
|
||||
),
|
||||
};
|
||||
let parent_dir = if args.is_build {
|
||||
&periphery_config().build_dir
|
||||
} else {
|
||||
&periphery_config().repo_dir
|
||||
};
|
||||
git::pull_or_clone(
|
||||
args,
|
||||
&periphery_config().repo_dir,
|
||||
parent_dir,
|
||||
token,
|
||||
&environment,
|
||||
&env_file_path,
|
||||
@@ -254,16 +270,25 @@ impl Resolve<super::Args> for RenameRepo {
|
||||
impl Resolve<super::Args> for DeleteRepo {
|
||||
#[instrument(name = "DeleteRepo")]
|
||||
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
||||
let DeleteRepo { name } = self;
|
||||
let DeleteRepo { name, is_build } = self;
|
||||
// If using custom clone path, it will be passed by core instead of name.
|
||||
// So the join will resolve to just the absolute path.
|
||||
let deleted =
|
||||
fs::remove_dir_all(periphery_config().repo_dir.join(&name))
|
||||
.await;
|
||||
let msg = match deleted {
|
||||
Ok(_) => format!("Deleted Repo {name}"),
|
||||
Err(_) => format!("No Repo at {name} to delete"),
|
||||
let root = if is_build {
|
||||
&periphery_config().build_dir
|
||||
} else {
|
||||
&periphery_config().repo_dir
|
||||
};
|
||||
Ok(Log::simple("Delete Repo on Host", msg))
|
||||
let full_path = root.join(&name);
|
||||
let deleted =
|
||||
fs::remove_dir_all(&full_path).await.with_context(|| {
|
||||
format!("Failed to delete repo at {full_path:?}")
|
||||
});
|
||||
let log = match deleted {
|
||||
Ok(_) => {
|
||||
Log::simple("Delete repo", format!("Deleted Repo {name}"))
|
||||
}
|
||||
Err(e) => Log::error("Delete repo", format_serror(&e.into())),
|
||||
};
|
||||
Ok(log)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ pub enum PeripheryRequest {
|
||||
DeleteRepo(DeleteRepo),
|
||||
|
||||
// Build
|
||||
GetDockerfileContentsOnHost(GetDockerfileContentsOnHost),
|
||||
WriteDockerfileContentsToHost(WriteDockerfileContentsToHost),
|
||||
Build(Build),
|
||||
PruneBuilders(PruneBuilders),
|
||||
PruneBuildx(PruneBuildx),
|
||||
|
||||
@@ -35,8 +35,10 @@ pub fn periphery_config() -> &'static PeripheryConfig {
|
||||
|
||||
PeripheryConfig {
|
||||
port: env.periphery_port.unwrap_or(config.port),
|
||||
bind_ip: env.periphery_bind_ip.unwrap_or(config.bind_ip),
|
||||
repo_dir: env.periphery_repo_dir.unwrap_or(config.repo_dir),
|
||||
stack_dir: env.periphery_stack_dir.unwrap_or(config.stack_dir),
|
||||
build_dir: env.periphery_build_dir.unwrap_or(config.build_dir),
|
||||
stats_polling_rate: env
|
||||
.periphery_stats_polling_rate
|
||||
.unwrap_or(config.stats_polling_rate),
|
||||
|
||||
@@ -945,7 +945,7 @@ pub async fn docker_login(
|
||||
None => crate::helpers::registry_token(domain, account)?,
|
||||
};
|
||||
let log = async_run_command(&format!(
|
||||
"echo {registry_token} | docker login {domain} --username {account} --password-stdin",
|
||||
"echo {registry_token} | docker login {domain} --username '{account}' --password-stdin",
|
||||
))
|
||||
.await;
|
||||
if log.success() {
|
||||
|
||||
@@ -26,9 +26,11 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
stats::spawn_system_stats_polling_thread();
|
||||
|
||||
let socket_addr =
|
||||
SocketAddr::from_str(&format!("0.0.0.0:{}", config.port))
|
||||
.context("failed to parse socket addr")?;
|
||||
let addr = format!("{}:{}", config::periphery_config().bind_ip, config::periphery_config().port);
|
||||
|
||||
let socket_addr =
|
||||
SocketAddr::from_str(&addr)
|
||||
.context("failed to parse listen address")?;
|
||||
|
||||
let app = router::router()
|
||||
.into_make_service_with_connect_info::<SocketAddr>();
|
||||
|
||||
@@ -194,7 +194,7 @@ fn get_system_information(
|
||||
os: System::long_os_version(),
|
||||
kernel: System::kernel_version(),
|
||||
host_name: System::host_name(),
|
||||
core_count: sys.physical_core_count().map(|c| c as u32),
|
||||
core_count: System::physical_core_count().map(|c| c as u32),
|
||||
cpu_brand: sys
|
||||
.cpus()
|
||||
.iter()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Komodo
|
||||
*A system to build and deploy software across many servers*
|
||||
*A system to build and deploy software across many servers*. [https://komo.do](https://komo.do)
|
||||
|
||||
Full Docs: [https://docs.rs/komodo_client/latest/komodo_client](https://docs.rs/komodo_client/latest/komodo_client).
|
||||
Docs: [https://docs.rs/komodo_client/latest/komodo_client](https://docs.rs/komodo_client/latest/komodo_client).
|
||||
|
||||
This is a client library for the Komodo Core API.
|
||||
It contains:
|
||||
|
||||
@@ -294,6 +294,35 @@ pub type ListAllDockerContainersResponse = Vec<ContainerListItem>;
|
||||
|
||||
//
|
||||
|
||||
/// Gets a summary of data relating to all containers.
|
||||
/// Response: [GetDockerContainersSummaryResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(GetDockerContainersSummaryResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct GetDockerContainersSummary {}
|
||||
|
||||
/// Response for [GetDockerContainersSummary]
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct GetDockerContainersSummaryResponse {
|
||||
/// The total number of Containers
|
||||
pub total: u32,
|
||||
/// The number of Containers with Running state
|
||||
pub running: u32,
|
||||
/// The number of Containers with Stopped or Paused or Created state
|
||||
pub stopped: u32,
|
||||
/// The number of Containers with Restarting or Dead state
|
||||
pub unhealthy: u32,
|
||||
/// The number of Containers with Unknown state
|
||||
pub unknown: u32,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Inspect a docker container on the server. Response: [Container].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
|
||||
@@ -98,7 +98,6 @@ pub struct UpdateAction {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameAction {
|
||||
/// The id or name of the Action to rename.
|
||||
pub id: String,
|
||||
|
||||
@@ -95,7 +95,6 @@ pub struct UpdateAlerter {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameAlerter {
|
||||
/// The id or name of the Alerter to rename.
|
||||
pub id: String,
|
||||
|
||||
@@ -98,7 +98,6 @@ pub struct UpdateBuild {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameBuild {
|
||||
/// The id or name of the Build to rename.
|
||||
pub id: String,
|
||||
@@ -108,6 +107,24 @@ pub struct RenameBuild {
|
||||
|
||||
//
|
||||
|
||||
/// Update dockerfile contents in Files on Server or Git Repo mode. Response: [Update].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
pub struct WriteBuildFileContents {
|
||||
/// The name or id of the target Build.
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub build: String,
|
||||
/// The dockerfile contents to write.
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Trigger a refresh of the cached latest hash and message.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
|
||||
@@ -97,7 +97,6 @@ pub struct UpdateBuilder {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameBuilder {
|
||||
/// The id or name of the Builder to rename.
|
||||
pub id: String,
|
||||
|
||||
@@ -122,7 +122,6 @@ pub struct UpdateDeployment {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameDeployment {
|
||||
/// The id of the deployment to rename.
|
||||
pub id: String,
|
||||
|
||||
@@ -109,7 +109,6 @@ pub type UpdateProcedureResponse = Procedure;
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameProcedure {
|
||||
/// The id or name of the Procedure to rename.
|
||||
pub id: String,
|
||||
|
||||
@@ -101,7 +101,6 @@ pub struct UpdateRepo {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameRepo {
|
||||
/// The id or name of the Repo to rename.
|
||||
pub id: String,
|
||||
|
||||
@@ -79,7 +79,6 @@ pub struct UpdateServer {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameServer {
|
||||
/// The id or name of the Server to rename.
|
||||
pub id: String,
|
||||
@@ -100,7 +99,6 @@ pub struct RenameServer {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct CreateNetwork {
|
||||
/// Server Id or name
|
||||
pub server: String,
|
||||
|
||||
@@ -97,7 +97,6 @@ pub struct UpdateServerTemplate {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameServerTemplate {
|
||||
/// The id or name of the ServerTemplate to rename.
|
||||
pub id: String,
|
||||
|
||||
@@ -100,7 +100,6 @@ pub struct UpdateStack {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameStack {
|
||||
/// The id of the stack to rename.
|
||||
pub id: String,
|
||||
@@ -118,7 +117,6 @@ pub struct RenameStack {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct WriteStackFileContents {
|
||||
/// The name or id of the target Stack.
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
|
||||
@@ -99,7 +99,6 @@ pub struct UpdateResourceSync {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct RenameResourceSync {
|
||||
/// The id or name of the ResourceSync to rename.
|
||||
pub id: String,
|
||||
@@ -133,7 +132,6 @@ pub struct RefreshResourceSyncPending {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct WriteSyncFileContents {
|
||||
/// The name or id of the target Sync.
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
@@ -166,7 +164,6 @@ pub struct WriteSyncFileContents {
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Update)]
|
||||
#[error(serror::Error)]
|
||||
#[error(serror::Error)]
|
||||
pub struct CommitSync {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
|
||||
@@ -63,6 +63,7 @@ pub type _PartialActionConfig = PartialActionConfig;
|
||||
#[partial(skip_serializing_none, from, diff)]
|
||||
pub struct ActionConfig {
|
||||
/// Typescript file contents using pre-initialized `komodo` client.
|
||||
/// Supports variable / secret interpolation.
|
||||
#[serde(default, deserialize_with = "file_contents_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
|
||||
@@ -111,6 +111,9 @@ pub enum AlerterEndpoint {
|
||||
|
||||
/// Send alert to a Discord app
|
||||
Discord(DiscordAlerterEndpoint),
|
||||
|
||||
/// Send alert to Ntfy
|
||||
Ntfy(NtfyAlerterEndpoint),
|
||||
}
|
||||
|
||||
impl Default for AlerterEndpoint {
|
||||
@@ -195,6 +198,31 @@ fn default_discord_url() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// Configuration for a Ntfy alerter.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Serialize, Deserialize, Builder,
|
||||
)]
|
||||
pub struct NtfyAlerterEndpoint {
|
||||
/// The ntfy topic URL
|
||||
#[serde(default = "default_ntfy_url")]
|
||||
#[builder(default = "default_ntfy_url()")]
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl Default for NtfyAlerterEndpoint {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: default_ntfy_url(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_ntfy_url() -> String {
|
||||
String::from("http://localhost:8080/komodo")
|
||||
}
|
||||
|
||||
|
||||
// QUERY
|
||||
|
||||
#[typeshare]
|
||||
|
||||
@@ -32,26 +32,31 @@ pub type BuildListItem = ResourceListItem<BuildListItemInfo>;
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuildListItemInfo {
|
||||
/// State of the build. Reflects whether most recent build successful.
|
||||
pub state: BuildState,
|
||||
/// Unix timestamp in milliseconds of last build
|
||||
pub last_built_at: I64,
|
||||
/// The current version of the build
|
||||
pub version: Version,
|
||||
/// The builder attached to build.
|
||||
pub builder_id: String,
|
||||
|
||||
/// Whether build is in files on host mode.
|
||||
pub files_on_host: bool,
|
||||
|
||||
/// The git provider domain
|
||||
pub git_provider: String,
|
||||
/// The image registry domain
|
||||
pub image_registry_domain: String,
|
||||
pub git_provider: Option<String>,
|
||||
/// The repo used as the source of the build
|
||||
pub repo: String,
|
||||
pub repo: Option<String>,
|
||||
/// The branch of the repo
|
||||
pub branch: String,
|
||||
/// State of the build. Reflects whether most recent build successful.
|
||||
pub state: BuildState,
|
||||
pub branch: Option<String>,
|
||||
/// Latest built short commit hash, or null.
|
||||
pub built_hash: Option<String>,
|
||||
/// Latest short commit hash, or null. Only for repo based stacks
|
||||
pub latest_hash: Option<String>,
|
||||
|
||||
/// The image registry domain
|
||||
pub image_registry_domain: Option<String>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
@@ -73,11 +78,26 @@ pub enum BuildState {
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct BuildInfo {
|
||||
/// The timestamp build was last built.
|
||||
pub last_built_at: I64,
|
||||
|
||||
/// Latest built short commit hash, or null.
|
||||
pub built_hash: Option<String>,
|
||||
/// Latest built commit message, or null. Only for repo based stacks
|
||||
pub built_message: Option<String>,
|
||||
/// The last built dockerfile contents.
|
||||
/// This is updated whenever Komodo successfully runs the build.
|
||||
pub built_contents: Option<String>,
|
||||
|
||||
/// The absolute path to the file
|
||||
pub remote_path: Option<String>,
|
||||
/// The remote dockerfile contents, whether on host or in repo.
|
||||
/// This is updated whenever Komodo refreshes the build cache.
|
||||
/// It will be empty if the dockerfile is defined directly in the build config.
|
||||
pub remote_contents: Option<String>,
|
||||
/// If there was an error in getting the remote contents, it will be here.
|
||||
pub remote_error: Option<String>,
|
||||
|
||||
/// Latest remote short commit hash, or null.
|
||||
pub latest_hash: Option<String>,
|
||||
/// Latest remote commit message, or null
|
||||
@@ -121,8 +141,8 @@ pub struct BuildConfig {
|
||||
#[builder(default)]
|
||||
pub image_name: String,
|
||||
|
||||
/// An extra tag put before the build version, for the image pushed to the repository.
|
||||
/// Eg. in image tag of `aarch64` would push to moghtech/komodo:1.13.2-aarch64.
|
||||
/// An extra tag put after the build version, for the image pushed to the repository.
|
||||
/// Eg. in image tag of `aarch64` would push to moghtech/komodo-core:1.13.2-aarch64.
|
||||
/// If this is empty, the image tag will just be the build version.
|
||||
///
|
||||
/// Can be used in conjunction with `image_name` to direct multiple builds
|
||||
@@ -192,10 +212,13 @@ pub struct BuildConfig {
|
||||
#[builder(default)]
|
||||
pub webhook_secret: String,
|
||||
|
||||
/// The optional command run after repo clone and before docker build.
|
||||
/// If this is checked, the build will source the files on the host.
|
||||
/// Use `build_path` and `dockerfile_path` to specify the path on the host.
|
||||
/// This is useful for those who wish to setup their files on the host,
|
||||
/// rather than defining the contents in UI or in a git repo.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub pre_build: SystemCommand,
|
||||
pub files_on_host: bool,
|
||||
|
||||
/// The path of the docker build context relative to the root of the repo.
|
||||
/// Default: "." (the root of the repo).
|
||||
@@ -238,6 +261,17 @@ pub struct BuildConfig {
|
||||
#[builder(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
/// The optional command run after repo clone and before docker build.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub pre_build: SystemCommand,
|
||||
|
||||
/// UI defined dockerfile contents.
|
||||
/// Supports variable / secret interpolation.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub dockerfile: String,
|
||||
|
||||
/// Docker build arguments.
|
||||
///
|
||||
/// These values are visible in the final image by running `docker inspect`.
|
||||
@@ -338,6 +372,8 @@ impl Default for BuildConfig {
|
||||
image_registry: Default::default(),
|
||||
webhook_enabled: default_webhook_enabled(),
|
||||
webhook_secret: Default::default(),
|
||||
dockerfile: Default::default(),
|
||||
files_on_host: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ pub struct Env {
|
||||
pub komodo_host: Option<String>,
|
||||
/// Override `port`
|
||||
pub komodo_port: Option<u16>,
|
||||
/// Override `bind_ip`
|
||||
pub komodo_bind_ip: Option<String>,
|
||||
/// Override `passkey`
|
||||
pub komodo_passkey: Option<String>,
|
||||
/// Override `passkey` with file
|
||||
@@ -256,6 +258,11 @@ pub struct CoreConfig {
|
||||
#[serde(default = "default_core_port")]
|
||||
pub port: u16,
|
||||
|
||||
/// IP address the core server binds to.
|
||||
/// Default: [::].
|
||||
#[serde(default = "default_core_bind_ip")]
|
||||
pub bind_ip: String,
|
||||
|
||||
/// Sent in auth header with req to periphery.
|
||||
/// Should be some secure hash, maybe 20-40 chars.
|
||||
pub passkey: String,
|
||||
@@ -521,6 +528,10 @@ fn default_core_port() -> u16 {
|
||||
9120
|
||||
}
|
||||
|
||||
fn default_core_bind_ip() -> String {
|
||||
"[::]".to_string()
|
||||
}
|
||||
|
||||
fn default_frontend_path() -> String {
|
||||
"/app/frontend".to_string()
|
||||
}
|
||||
@@ -571,6 +582,7 @@ impl CoreConfig {
|
||||
title: config.title,
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
bind_ip: config.bind_ip,
|
||||
passkey: empty_or_redacted(&config.passkey),
|
||||
first_server: config.first_server,
|
||||
frontend_path: config.frontend_path,
|
||||
|
||||
@@ -114,10 +114,14 @@ pub struct Env {
|
||||
|
||||
/// Override `port`
|
||||
pub periphery_port: Option<u16>,
|
||||
/// Override `bind_ip`
|
||||
pub periphery_bind_ip: Option<String>,
|
||||
/// Override `repo_dir`
|
||||
pub periphery_repo_dir: Option<PathBuf>,
|
||||
/// Override `stack_dir`
|
||||
pub periphery_stack_dir: Option<PathBuf>,
|
||||
/// Override `build_dir`
|
||||
pub periphery_build_dir: Option<PathBuf>,
|
||||
/// Override `stats_polling_rate`
|
||||
pub periphery_stats_polling_rate: Option<Timelength>,
|
||||
/// Override `legacy_compose_cli`
|
||||
@@ -162,6 +166,11 @@ pub struct PeripheryConfig {
|
||||
#[serde(default = "default_periphery_port")]
|
||||
pub port: u16,
|
||||
|
||||
/// IP address the periphery server binds to.
|
||||
/// Default: [::].
|
||||
#[serde(default = "default_periphery_bind_ip")]
|
||||
pub bind_ip: String,
|
||||
|
||||
/// The system directory where Komodo managed repos will be cloned.
|
||||
/// Default: `/etc/komodo/repos`
|
||||
#[serde(default = "default_repo_dir")]
|
||||
@@ -172,6 +181,11 @@ pub struct PeripheryConfig {
|
||||
#[serde(default = "default_stack_dir")]
|
||||
pub stack_dir: PathBuf,
|
||||
|
||||
/// The system directory where builds will managed.
|
||||
/// Default: `/etc/komodo/builds`
|
||||
#[serde(default = "default_build_dir")]
|
||||
pub build_dir: PathBuf,
|
||||
|
||||
/// The rate at which the system stats will be polled to update the cache.
|
||||
/// Default: `5-sec`
|
||||
#[serde(default = "default_stats_polling_rate")]
|
||||
@@ -244,6 +258,10 @@ fn default_periphery_port() -> u16 {
|
||||
8120
|
||||
}
|
||||
|
||||
fn default_periphery_bind_ip() -> String {
|
||||
"[::]".to_string()
|
||||
}
|
||||
|
||||
fn default_repo_dir() -> PathBuf {
|
||||
"/etc/komodo/repos".parse().unwrap()
|
||||
}
|
||||
@@ -252,6 +270,10 @@ fn default_stack_dir() -> PathBuf {
|
||||
"/etc/komodo/stacks".parse().unwrap()
|
||||
}
|
||||
|
||||
fn default_build_dir() -> PathBuf {
|
||||
"/etc/komodo/builds".parse().unwrap()
|
||||
}
|
||||
|
||||
fn default_stats_polling_rate() -> Timelength {
|
||||
Timelength::FiveSeconds
|
||||
}
|
||||
@@ -272,8 +294,10 @@ impl Default for PeripheryConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: default_periphery_port(),
|
||||
bind_ip: default_periphery_bind_ip(),
|
||||
repo_dir: default_repo_dir(),
|
||||
stack_dir: default_stack_dir(),
|
||||
build_dir: default_build_dir(),
|
||||
stats_polling_rate: default_stats_polling_rate(),
|
||||
legacy_compose_cli: Default::default(),
|
||||
logging: Default::default(),
|
||||
@@ -295,8 +319,10 @@ impl PeripheryConfig {
|
||||
pub fn sanitized(&self) -> PeripheryConfig {
|
||||
PeripheryConfig {
|
||||
port: self.port,
|
||||
bind_ip: self.bind_ip.clone(),
|
||||
repo_dir: self.repo_dir.clone(),
|
||||
stack_dir: self.stack_dir.clone(),
|
||||
build_dir: self.build_dir.clone(),
|
||||
stats_polling_rate: self.stats_polling_rate,
|
||||
legacy_compose_cli: self.legacy_compose_cli,
|
||||
logging: self.logging.clone(),
|
||||
|
||||
@@ -118,11 +118,12 @@ pub fn all_logs_success(logs: &[update::Log]) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn optional_string(string: &str) -> Option<String> {
|
||||
pub fn optional_string(string: impl Into<String>) -> Option<String> {
|
||||
let string = string.into();
|
||||
if string.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(string.to_string())
|
||||
Some(string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,6 +395,8 @@ pub struct CloneArgs {
|
||||
pub branch: String,
|
||||
/// Specific commit hash. Optional
|
||||
pub commit: Option<String>,
|
||||
/// Use PERIPHERY_BUILD_DIR as the parent folder for the clone.
|
||||
pub is_build: bool,
|
||||
/// The clone destination path
|
||||
pub destination: Option<String>,
|
||||
/// Command to run after the repo has been cloned
|
||||
@@ -459,8 +462,9 @@ impl From<&self::build::Build> for CloneArgs {
|
||||
branch: optional_string(&build.config.branch)
|
||||
.unwrap_or_else(|| String::from("main")),
|
||||
commit: optional_string(&build.config.commit),
|
||||
is_build: true,
|
||||
destination: None,
|
||||
on_clone: build.config.pre_build.clone().into_option(),
|
||||
on_clone: None,
|
||||
on_pull: None,
|
||||
https: build.config.git_https,
|
||||
account: optional_string(&build.config.git_account),
|
||||
@@ -478,6 +482,7 @@ impl From<&self::repo::Repo> for CloneArgs {
|
||||
branch: optional_string(&repo.config.branch)
|
||||
.unwrap_or_else(|| String::from("main")),
|
||||
commit: optional_string(&repo.config.commit),
|
||||
is_build: false,
|
||||
destination: optional_string(&repo.config.path),
|
||||
on_clone: repo.config.on_clone.clone().into_option(),
|
||||
on_pull: repo.config.on_pull.clone().into_option(),
|
||||
@@ -497,6 +502,7 @@ impl From<&self::sync::ResourceSync> for CloneArgs {
|
||||
branch: optional_string(&sync.config.branch)
|
||||
.unwrap_or_else(|| String::from("main")),
|
||||
commit: optional_string(&sync.config.commit),
|
||||
is_build: false,
|
||||
destination: None,
|
||||
on_clone: None,
|
||||
on_pull: None,
|
||||
@@ -516,6 +522,7 @@ impl From<&self::stack::Stack> for CloneArgs {
|
||||
branch: optional_string(&stack.config.branch)
|
||||
.unwrap_or_else(|| String::from("main")),
|
||||
commit: optional_string(&stack.config.commit),
|
||||
is_build: false,
|
||||
destination: None,
|
||||
on_clone: None,
|
||||
on_pull: None,
|
||||
@@ -713,6 +720,7 @@ pub enum Operation {
|
||||
DeleteBuild,
|
||||
RunBuild,
|
||||
CancelBuild,
|
||||
WriteDockerfile,
|
||||
|
||||
// repo
|
||||
CreateRepo,
|
||||
|
||||
@@ -62,6 +62,21 @@ pub struct Resource<Config: Default, Info: Default = ()> {
|
||||
pub base_permission: PermissionLevel,
|
||||
}
|
||||
|
||||
impl<C: Default, I: Default> Default for Resource<C, I> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: String::new(),
|
||||
name: String::from("temp-resource"),
|
||||
description: String::new(),
|
||||
updated_at: 0,
|
||||
tags: Vec::new(),
|
||||
info: I::default(),
|
||||
config: C::default(),
|
||||
base_permission: PermissionLevel::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ResourceListItem<Info> {
|
||||
|
||||
@@ -77,6 +77,13 @@ pub struct ServerConfig {
|
||||
#[partial_default(default_timeout_seconds())]
|
||||
pub timeout_seconds: I64,
|
||||
|
||||
/// An optional override passkey to use
|
||||
/// to authenticate with periphery agent.
|
||||
/// If this is empty, will use passkey in core config.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub passkey: String,
|
||||
|
||||
/// Sometimes the system stats reports a mount path that is not desired.
|
||||
/// Use this field to filter it out from the report.
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
@@ -240,6 +247,7 @@ impl Default for ServerConfig {
|
||||
send_mem_alerts: default_send_alerts(),
|
||||
send_disk_alerts: default_send_alerts(),
|
||||
region: Default::default(),
|
||||
passkey: Default::default(),
|
||||
cpu_warning: default_cpu_warning(),
|
||||
cpu_critical: default_cpu_critical(),
|
||||
mem_warning: default_mem_warning(),
|
||||
|
||||
@@ -269,47 +269,6 @@ pub struct StackConfig {
|
||||
#[builder(default)]
|
||||
pub skip_secret_interp: bool,
|
||||
|
||||
/// If this is checked, the stack will source the files on the host.
|
||||
/// Use `run_directory` and `file_paths` to specify the path on the host.
|
||||
/// This is useful for those who wish to setup their files on the host using SSH or similar,
|
||||
/// rather than defining the contents in UI or in a git repo.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub files_on_host: bool,
|
||||
|
||||
/// Directory to change to (`cd`) before running `docker compose up -d`.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub run_directory: String,
|
||||
|
||||
/// Add paths to compose files, relative to the run path.
|
||||
/// If this is empty, will use file `compose.yaml`.
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub file_paths: Vec<String>,
|
||||
|
||||
/// The name of the written environment file before `docker compose up`.
|
||||
/// Relative to the run directory root.
|
||||
/// Default: .env
|
||||
#[serde(default = "default_env_file_path")]
|
||||
#[builder(default = "default_env_file_path()")]
|
||||
#[partial_default(default_env_file_path())]
|
||||
pub env_file_path: String,
|
||||
|
||||
/// Add additional env files to attach with `--env-file`.
|
||||
/// Relative to the run directory root.
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub additional_env_files: Vec<String>,
|
||||
|
||||
/// The git provider domain. Default: github.com
|
||||
#[serde(default = "default_git_provider")]
|
||||
#[builder(default = "default_git_provider()")]
|
||||
@@ -373,6 +332,47 @@ pub struct StackConfig {
|
||||
#[builder(default)]
|
||||
pub webhook_force_deploy: bool,
|
||||
|
||||
/// If this is checked, the stack will source the files on the host.
|
||||
/// Use `run_directory` and `file_paths` to specify the path on the host.
|
||||
/// This is useful for those who wish to setup their files on the host,
|
||||
/// rather than defining the contents in UI or in a git repo.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub files_on_host: bool,
|
||||
|
||||
/// Directory to change to (`cd`) before running `docker compose up -d`.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub run_directory: String,
|
||||
|
||||
/// Add paths to compose files, relative to the run path.
|
||||
/// If this is empty, will use file `compose.yaml`.
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub file_paths: Vec<String>,
|
||||
|
||||
/// The name of the written environment file before `docker compose up`.
|
||||
/// Relative to the run directory root.
|
||||
/// Default: .env
|
||||
#[serde(default = "default_env_file_path")]
|
||||
#[builder(default = "default_env_file_path()")]
|
||||
#[partial_default(default_env_file_path())]
|
||||
pub env_file_path: String,
|
||||
|
||||
/// Add additional env files to attach with `--env-file`.
|
||||
/// Relative to the run directory root.
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub additional_env_files: Vec<String>,
|
||||
|
||||
/// Whether to send StackStateChange alerts for this stack.
|
||||
#[serde(default = "default_send_alerts")]
|
||||
#[builder(default = "default_send_alerts()")]
|
||||
@@ -434,6 +434,7 @@ pub struct StackConfig {
|
||||
/// The contents of the file directly, for management in the UI.
|
||||
/// If this is empty, it will fall back to checking git config for
|
||||
/// repo based compose file.
|
||||
/// Supports variable / secret interpolation.
|
||||
#[serde(default, deserialize_with = "file_contents_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! # Komodo
|
||||
//! *A system to build and deploy software across many servers*
|
||||
//! *A system to build and deploy software across many servers*. [**https://komo.do**](https://komo.do)
|
||||
//!
|
||||
//! This is a client library for the Komodo Core API.
|
||||
//! It contains:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Komodo
|
||||
|
||||
_A system to build and deploy software across many servers_
|
||||
_A system to build and deploy software across many servers_. [https://komo.do](https://komo.do)
|
||||
|
||||
```sh
|
||||
npm install komodo_client
|
||||
@@ -18,7 +18,7 @@ import { KomodoClient, Types } from "komodo_client";
|
||||
const komodo = KomodoClient("https://demo.komo.do", {
|
||||
type: "api-key",
|
||||
params: {
|
||||
api_key: "your_key",
|
||||
key: "your_key",
|
||||
secret: "your secret",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "komodo_client",
|
||||
"version": "1.16.13",
|
||||
"version": "1.17.1",
|
||||
"description": "Komodo client package",
|
||||
"homepage": "https://komo.do",
|
||||
"main": "dist/lib.js",
|
||||
"type": "module",
|
||||
"license": "GPL-3.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -220,6 +220,9 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
// Sleep for a bit before checking for websocket closed
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
// Sleep for a bit before retrying connection to avoid spam.
|
||||
await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// Sleep for a bit before retrying, maybe Komodo Core is down temporarily.
|
||||
|
||||
@@ -61,6 +61,7 @@ export type ReadResponses = {
|
||||
GetServer: Types.GetServerResponse;
|
||||
GetServerState: Types.GetServerStateResponse;
|
||||
GetPeripheryVersion: Types.GetPeripheryVersionResponse;
|
||||
GetDockerContainersSummary: Types.GetDockerContainersSummaryResponse;
|
||||
ListDockerContainers: Types.ListDockerContainersResponse;
|
||||
ListAllDockerContainers: Types.ListAllDockerContainersResponse;
|
||||
InspectDockerContainer: Types.InspectDockerContainerResponse;
|
||||
@@ -227,6 +228,7 @@ export type WriteResponses = {
|
||||
DeleteBuild: Types.Build;
|
||||
UpdateBuild: Types.Build;
|
||||
RenameBuild: Types.Update;
|
||||
WriteBuildFileContents: Types.Update;
|
||||
RefreshBuildCache: Types.NoData;
|
||||
CreateBuildWebhook: Types.CreateBuildWebhookResponse;
|
||||
DeleteBuildWebhook: Types.DeleteBuildWebhookResponse;
|
||||
|
||||
+125
-43
@@ -52,7 +52,10 @@ export interface Resource<Config, Info> {
|
||||
}
|
||||
|
||||
export interface ActionConfig {
|
||||
/** Typescript file contents using pre-initialized `komodo` client. */
|
||||
/**
|
||||
* Typescript file contents using pre-initialized `komodo` client.
|
||||
* Supports variable / secret interpolation.
|
||||
*/
|
||||
file_contents?: string;
|
||||
/** Whether incoming webhooks actually trigger action. */
|
||||
webhook_enabled: boolean;
|
||||
@@ -130,7 +133,9 @@ export type AlerterEndpoint =
|
||||
/** Send alert to a Slack app */
|
||||
| { type: "Slack", params: SlackAlerterEndpoint }
|
||||
/** Send alert to a Discord app */
|
||||
| { type: "Discord", params: DiscordAlerterEndpoint };
|
||||
| { type: "Discord", params: DiscordAlerterEndpoint }
|
||||
/** Send alert to Ntfy */
|
||||
| { type: "Ntfy", params: NtfyAlerterEndpoint };
|
||||
|
||||
/** Used to reference a specific resource across all resource types */
|
||||
export type ResourceTarget =
|
||||
@@ -210,11 +215,6 @@ export interface Version {
|
||||
patch: number;
|
||||
}
|
||||
|
||||
export interface SystemCommand {
|
||||
path?: string;
|
||||
command?: string;
|
||||
}
|
||||
|
||||
/** Configuration for an image registry */
|
||||
export interface ImageRegistryConfig {
|
||||
/**
|
||||
@@ -231,6 +231,11 @@ export interface ImageRegistryConfig {
|
||||
organization?: string;
|
||||
}
|
||||
|
||||
export interface SystemCommand {
|
||||
path?: string;
|
||||
command?: string;
|
||||
}
|
||||
|
||||
/** The build configuration. */
|
||||
export interface BuildConfig {
|
||||
/** Which builder is used to build the image. */
|
||||
@@ -252,8 +257,8 @@ export interface BuildConfig {
|
||||
*/
|
||||
image_name?: string;
|
||||
/**
|
||||
* An extra tag put before the build version, for the image pushed to the repository.
|
||||
* Eg. in image tag of `aarch64` would push to moghtech/komodo:1.13.2-aarch64.
|
||||
* An extra tag put after the build version, for the image pushed to the repository.
|
||||
* Eg. in image tag of `aarch64` would push to moghtech/komodo-core:1.13.2-aarch64.
|
||||
* If this is empty, the image tag will just be the build version.
|
||||
*
|
||||
* Can be used in conjunction with `image_name` to direct multiple builds
|
||||
@@ -292,8 +297,13 @@ export interface BuildConfig {
|
||||
* If its an empty string, use the default secret from the config.
|
||||
*/
|
||||
webhook_secret?: string;
|
||||
/** The optional command run after repo clone and before docker build. */
|
||||
pre_build?: SystemCommand;
|
||||
/**
|
||||
* If this is checked, the build will source the files on the host.
|
||||
* Use `build_path` and `dockerfile_path` to specify the path on the host.
|
||||
* This is useful for those who wish to setup their files on the host,
|
||||
* rather than defining the contents in UI or in a git repo.
|
||||
*/
|
||||
files_on_host?: boolean;
|
||||
/**
|
||||
* The path of the docker build context relative to the root of the repo.
|
||||
* Default: "." (the root of the repo).
|
||||
@@ -309,6 +319,13 @@ export interface BuildConfig {
|
||||
use_buildx?: boolean;
|
||||
/** Any extra docker cli arguments to be included in the build command */
|
||||
extra_args?: string[];
|
||||
/** The optional command run after repo clone and before docker build. */
|
||||
pre_build?: SystemCommand;
|
||||
/**
|
||||
* UI defined dockerfile contents.
|
||||
* Supports variable / secret interpolation.
|
||||
*/
|
||||
dockerfile?: string;
|
||||
/**
|
||||
* Docker build arguments.
|
||||
*
|
||||
@@ -333,11 +350,27 @@ export interface BuildConfig {
|
||||
}
|
||||
|
||||
export interface BuildInfo {
|
||||
/** The timestamp build was last built. */
|
||||
last_built_at: I64;
|
||||
/** Latest built short commit hash, or null. */
|
||||
built_hash?: string;
|
||||
/** Latest built commit message, or null. Only for repo based stacks */
|
||||
built_message?: string;
|
||||
/**
|
||||
* The last built dockerfile contents.
|
||||
* This is updated whenever Komodo successfully runs the build.
|
||||
*/
|
||||
built_contents?: string;
|
||||
/** The absolute path to the file */
|
||||
remote_path?: string;
|
||||
/**
|
||||
* The remote dockerfile contents, whether on host or in repo.
|
||||
* This is updated whenever Komodo refreshes the build cache.
|
||||
* It will be empty if the dockerfile is defined directly in the build config.
|
||||
*/
|
||||
remote_contents?: string;
|
||||
/** If there was an error in getting the remote contents, it will be here. */
|
||||
remote_error?: string;
|
||||
/** Latest remote short commit hash, or null. */
|
||||
latest_hash?: string;
|
||||
/** Latest remote commit message, or null */
|
||||
@@ -358,26 +391,28 @@ export enum BuildState {
|
||||
}
|
||||
|
||||
export interface BuildListItemInfo {
|
||||
/** State of the build. Reflects whether most recent build successful. */
|
||||
state: BuildState;
|
||||
/** Unix timestamp in milliseconds of last build */
|
||||
last_built_at: I64;
|
||||
/** The current version of the build */
|
||||
version: Version;
|
||||
/** The builder attached to build. */
|
||||
builder_id: string;
|
||||
/** Whether build is in files on host mode. */
|
||||
files_on_host: boolean;
|
||||
/** The git provider domain */
|
||||
git_provider: string;
|
||||
/** The image registry domain */
|
||||
image_registry_domain: string;
|
||||
git_provider?: string;
|
||||
/** The repo used as the source of the build */
|
||||
repo: string;
|
||||
repo?: string;
|
||||
/** The branch of the repo */
|
||||
branch: string;
|
||||
/** State of the build. Reflects whether most recent build successful. */
|
||||
state: BuildState;
|
||||
branch?: string;
|
||||
/** Latest built short commit hash, or null. */
|
||||
built_hash?: string;
|
||||
/** Latest short commit hash, or null. Only for repo based stacks */
|
||||
latest_hash?: string;
|
||||
/** The image registry domain */
|
||||
image_registry_domain?: string;
|
||||
}
|
||||
|
||||
export type BuildListItem = ResourceListItem<BuildListItemInfo>;
|
||||
@@ -1520,6 +1555,12 @@ export interface ServerConfig {
|
||||
* default: 2
|
||||
*/
|
||||
timeout_seconds: I64;
|
||||
/**
|
||||
* An optional override passkey to use
|
||||
* to authenticate with periphery agent.
|
||||
* If this is empty, will use passkey in core config.
|
||||
*/
|
||||
passkey?: string;
|
||||
/**
|
||||
* Sometimes the system stats reports a mount path that is not desired.
|
||||
* Use this field to filter it out from the report.
|
||||
@@ -1633,31 +1674,6 @@ export interface StackConfig {
|
||||
destroy_before_deploy?: boolean;
|
||||
/** Whether to skip secret interpolation into the stack environment variables. */
|
||||
skip_secret_interp?: boolean;
|
||||
/**
|
||||
* If this is checked, the stack will source the files on the host.
|
||||
* Use `run_directory` and `file_paths` to specify the path on the host.
|
||||
* This is useful for those who wish to setup their files on the host using SSH or similar,
|
||||
* rather than defining the contents in UI or in a git repo.
|
||||
*/
|
||||
files_on_host?: boolean;
|
||||
/** Directory to change to (`cd`) before running `docker compose up -d`. */
|
||||
run_directory?: string;
|
||||
/**
|
||||
* Add paths to compose files, relative to the run path.
|
||||
* If this is empty, will use file `compose.yaml`.
|
||||
*/
|
||||
file_paths?: string[];
|
||||
/**
|
||||
* The name of the written environment file before `docker compose up`.
|
||||
* Relative to the run directory root.
|
||||
* Default: .env
|
||||
*/
|
||||
env_file_path: string;
|
||||
/**
|
||||
* Add additional env files to attach with `--env-file`.
|
||||
* Relative to the run directory root.
|
||||
*/
|
||||
additional_env_files?: string[];
|
||||
/** The git provider domain. Default: github.com */
|
||||
git_provider: string;
|
||||
/**
|
||||
@@ -1697,6 +1713,31 @@ export interface StackConfig {
|
||||
* If this option is enabled, will always run `DeployStack` without diffing.
|
||||
*/
|
||||
webhook_force_deploy?: boolean;
|
||||
/**
|
||||
* If this is checked, the stack will source the files on the host.
|
||||
* Use `run_directory` and `file_paths` to specify the path on the host.
|
||||
* This is useful for those who wish to setup their files on the host,
|
||||
* rather than defining the contents in UI or in a git repo.
|
||||
*/
|
||||
files_on_host?: boolean;
|
||||
/** Directory to change to (`cd`) before running `docker compose up -d`. */
|
||||
run_directory?: string;
|
||||
/**
|
||||
* Add paths to compose files, relative to the run path.
|
||||
* If this is empty, will use file `compose.yaml`.
|
||||
*/
|
||||
file_paths?: string[];
|
||||
/**
|
||||
* The name of the written environment file before `docker compose up`.
|
||||
* Relative to the run directory root.
|
||||
* Default: .env
|
||||
*/
|
||||
env_file_path: string;
|
||||
/**
|
||||
* Add additional env files to attach with `--env-file`.
|
||||
* Relative to the run directory root.
|
||||
*/
|
||||
additional_env_files?: string[];
|
||||
/** Whether to send StackStateChange alerts for this stack. */
|
||||
send_alerts: boolean;
|
||||
/** Used with `registry_account` to login to a registry before docker compose up. */
|
||||
@@ -1728,6 +1769,7 @@ export interface StackConfig {
|
||||
* The contents of the file directly, for management in the UI.
|
||||
* If this is empty, it will fall back to checking git config for
|
||||
* repo based compose file.
|
||||
* Supports variable / secret interpolation.
|
||||
*/
|
||||
file_contents?: string;
|
||||
/**
|
||||
@@ -2053,6 +2095,7 @@ export enum Operation {
|
||||
DeleteBuild = "DeleteBuild",
|
||||
RunBuild = "RunBuild",
|
||||
CancelBuild = "CancelBuild",
|
||||
WriteDockerfile = "WriteDockerfile",
|
||||
CreateRepo = "CreateRepo",
|
||||
UpdateRepo = "UpdateRepo",
|
||||
RenameRepo = "RenameRepo",
|
||||
@@ -4007,6 +4050,8 @@ export interface CloneArgs {
|
||||
branch: string;
|
||||
/** Specific commit hash. Optional */
|
||||
commit?: string;
|
||||
/** Use PERIPHERY_BUILD_DIR as the parent folder for the clone. */
|
||||
is_build: boolean;
|
||||
/** The clone destination path */
|
||||
destination?: string;
|
||||
/** Command to run after the repo has been cloned */
|
||||
@@ -5169,6 +5214,27 @@ export interface GetDeploymentsSummaryResponse {
|
||||
unknown: I64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a summary of data relating to all containers.
|
||||
* Response: [GetDockerContainersSummaryResponse].
|
||||
*/
|
||||
export interface GetDockerContainersSummary {
|
||||
}
|
||||
|
||||
/** Response for [GetDockerContainersSummary] */
|
||||
export interface GetDockerContainersSummaryResponse {
|
||||
/** The total number of Containers */
|
||||
total: number;
|
||||
/** The number of Containers with Running state */
|
||||
running: number;
|
||||
/** The number of Containers with Stopped or Paused or Created state */
|
||||
stopped: number;
|
||||
/** The number of Containers with Restarting or Dead state */
|
||||
unhealthy: number;
|
||||
/** The number of Containers with Unknown state */
|
||||
unknown: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific docker registry account.
|
||||
* Response: [GetDockerRegistryAccountResponse].
|
||||
@@ -6354,6 +6420,12 @@ export interface NameAndId {
|
||||
id: string;
|
||||
}
|
||||
|
||||
/** Configuration for a Ntfy alerter. */
|
||||
export interface NtfyAlerterEndpoint {
|
||||
/** The ntfy topic URL */
|
||||
url: string;
|
||||
}
|
||||
|
||||
/** Pauses all containers on the target server. Response: [Update] */
|
||||
export interface PauseAllContainers {
|
||||
/** Name or id */
|
||||
@@ -7498,6 +7570,14 @@ export interface UrlBuilderConfig {
|
||||
passkey?: string;
|
||||
}
|
||||
|
||||
/** Update dockerfile contents in Files on Server or Git Repo mode. Response: [Update]. */
|
||||
export interface WriteBuildFileContents {
|
||||
/** The name or id of the target Build. */
|
||||
build: string;
|
||||
/** The dockerfile contents to write. */
|
||||
contents: string;
|
||||
}
|
||||
|
||||
/** Update file contents in Files on Server or Git Repo mode. Response: [Update]. */
|
||||
export interface WriteStackFileContents {
|
||||
/** The name or id of the target Stack. */
|
||||
@@ -7648,6 +7728,7 @@ export type ReadRequest =
|
||||
| { type: "InspectDockerImage", params: InspectDockerImage }
|
||||
| { type: "ListDockerImageHistory", params: ListDockerImageHistory }
|
||||
| { type: "InspectDockerVolume", params: InspectDockerVolume }
|
||||
| { type: "GetDockerContainersSummary", params: GetDockerContainersSummary }
|
||||
| { type: "ListAllDockerContainers", params: ListAllDockerContainers }
|
||||
| { type: "ListDockerContainers", params: ListDockerContainers }
|
||||
| { type: "ListDockerNetworks", params: ListDockerNetworks }
|
||||
@@ -7763,6 +7844,7 @@ export type WriteRequest =
|
||||
| { type: "DeleteBuild", params: DeleteBuild }
|
||||
| { type: "UpdateBuild", params: UpdateBuild }
|
||||
| { type: "RenameBuild", params: RenameBuild }
|
||||
| { type: "WriteBuildFileContents", params: WriteBuildFileContents }
|
||||
| { type: "RefreshBuildCache", params: RefreshBuildCache }
|
||||
| { type: "CreateBuildWebhook", params: CreateBuildWebhook }
|
||||
| { type: "DeleteBuildWebhook", params: DeleteBuildWebhook }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use komodo_client::entities::update::Log;
|
||||
use komodo_client::entities::{FileContents, update::Log};
|
||||
use resolver_api::Resolve;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -21,6 +21,42 @@ pub type BuildResponse = Vec<Log>;
|
||||
|
||||
//
|
||||
|
||||
/// Get the dockerfile contents on the host, for builds using
|
||||
/// `files_on_host`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
|
||||
#[response(GetDockerfileContentsOnHostResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct GetDockerfileContentsOnHost {
|
||||
/// The name of the build
|
||||
pub name: String,
|
||||
/// The build path for the build.
|
||||
pub build_path: String,
|
||||
/// The dockerfile path for the build, relative to the build_path
|
||||
pub dockerfile_path: String,
|
||||
}
|
||||
|
||||
pub type GetDockerfileContentsOnHostResponse = FileContents;
|
||||
|
||||
//
|
||||
|
||||
/// Write the dockerfile contents to the file on the host, for build using
|
||||
/// `files_on_host`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
|
||||
#[response(Log)]
|
||||
#[error(serror::Error)]
|
||||
pub struct WriteDockerfileContentsToHost {
|
||||
/// The name of the build
|
||||
pub name: String,
|
||||
/// The build path for the build.
|
||||
pub build_path: String,
|
||||
/// The dockerfile path for the build, relative to the build_path
|
||||
pub dockerfile_path: String,
|
||||
/// The contents to write.
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
|
||||
#[response(Log)]
|
||||
#[error(serror::Error)]
|
||||
|
||||
@@ -106,4 +106,6 @@ pub struct RenameRepo {
|
||||
#[error(serror::Error)]
|
||||
pub struct DeleteRepo {
|
||||
pub name: String,
|
||||
/// Clears
|
||||
pub is_build: bool,
|
||||
}
|
||||
|
||||
+7
-1
@@ -122,13 +122,19 @@ PERIPHERY_PASSKEYS=${KOMODO_PASSKEY}
|
||||
## Specify the root directory used by Periphery agent.
|
||||
PERIPHERY_ROOT_DIRECTORY=/etc/komodo
|
||||
|
||||
PERIPHERY_REPO_DIR=$PERIPHERY_ROOT_DIRECTORY/repos
|
||||
PERIPHERY_STACK_DIR=$PERIPHERY_ROOT_DIRECTORY/stacks
|
||||
PERIPHERY_BUILD_DIR=$PERIPHERY_ROOT_DIRECTORY/builds
|
||||
|
||||
## Enable SSL using self signed certificates.
|
||||
## Connect to Periphery at https://address:8120.
|
||||
PERIPHERY_SSL_ENABLED=true
|
||||
PERIPHERY_SSL_KEY_FILE=$PERIPHERY_ROOT_DIRECTORY/ssl/key.pem
|
||||
PERIPHERY_SSL_CERT_FILE=$PERIPHERY_ROOT_DIRECTORY/ssl/cert.pem
|
||||
|
||||
## If the disk size is overreporting, can use one of these to
|
||||
## whitelist / blacklist the disks to filter them, whichever is easier.
|
||||
## Accepts comma separated list of paths.
|
||||
## Usually whitelisting just /etc/hostname gives correct size.
|
||||
PERIPHERY_INCLUDE_DISK_MOUNTS=/etc/hostname
|
||||
# PERIPHERY_EXCLUDE_DISK_MOUNTS=/snap,/etc/repos
|
||||
# PERIPHERY_EXCLUDE_DISK_MOUNTS=/snap,/etc/repos
|
||||
|
||||
@@ -16,8 +16,6 @@ services:
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
# ports:
|
||||
# - 27017:27017
|
||||
volumes:
|
||||
@@ -28,7 +26,7 @@ services:
|
||||
MONGO_INITDB_ROOT_PASSWORD: ${KOMODO_DB_PASSWORD}
|
||||
|
||||
core:
|
||||
image: ghcr.io/moghtech/komodo:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
@@ -36,8 +34,6 @@ services:
|
||||
- mongo
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
ports:
|
||||
- 9120:9120
|
||||
env_file: ./compose.env
|
||||
@@ -61,20 +57,13 @@ services:
|
||||
## or deploy the Periphery binary with systemd using
|
||||
## https://github.com/moghtech/komodo/tree/main/scripts
|
||||
periphery:
|
||||
image: ghcr.io/moghtech/periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
env_file: ./compose.env
|
||||
environment:
|
||||
PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos
|
||||
PERIPHERY_STACK_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/stacks
|
||||
PERIPHERY_SSL_KEY_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/key.pem
|
||||
PERIPHERY_SSL_CERT_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/cert.pem
|
||||
volumes:
|
||||
## Mount external docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -91,7 +80,4 @@ volumes:
|
||||
mongo-data:
|
||||
mongo-config:
|
||||
# Core
|
||||
repo-cache:
|
||||
|
||||
networks:
|
||||
default: {}
|
||||
repo-cache:
|
||||
@@ -0,0 +1,46 @@
|
||||
####################################
|
||||
# 🦎 KOMODO COMPOSE - PERIPHERY 🦎 #
|
||||
####################################
|
||||
|
||||
## This compose file will deploy:
|
||||
## 1. Komodo Periphery
|
||||
|
||||
services:
|
||||
periphery:
|
||||
image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
## https://komo.do/docs/connect-servers#configuration
|
||||
environment:
|
||||
PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos
|
||||
PERIPHERY_STACK_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/stacks
|
||||
PERIPHERY_BUILD_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/builds
|
||||
PERIPHERY_SSL_KEY_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/key.pem
|
||||
PERIPHERY_SSL_CERT_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/cert.pem
|
||||
## Pass the same passkey as used by the Komodo Core connecting to this Periphery agent.
|
||||
PERIPHERY_PASSKEYS: abc123
|
||||
## If the disk size is overreporting, can use one of these to
|
||||
## whitelist / blacklist the disks to filter them, whichever is easier.
|
||||
## Accepts comma separated list of paths.
|
||||
## Usually whitelisting just /etc/hostname gives correct size for single root disk.
|
||||
PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname
|
||||
# PERIPHERY_EXCLUDE_DISK_MOUNTS: /snap,/etc/repos
|
||||
volumes:
|
||||
## Mount external docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
## Allow Periphery to see processes outside of container
|
||||
- /proc:/proc
|
||||
## Specify the Periphery agent root directory.
|
||||
## Must be the same inside and outside the container,
|
||||
## or docker will get confused. See https://github.com/moghtech/komodo/discussions/180.
|
||||
## Default: /etc/komodo.
|
||||
- ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}
|
||||
## If periphery is being run remote from the core server, ports need
|
||||
## to be exposed on the host.
|
||||
# ports:
|
||||
# - 8120:8120
|
||||
## If you want to use a custom periphery config file, use command to pass it to periphery.
|
||||
# command: periphery --config-path ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/periphery.config.toml
|
||||
@@ -15,8 +15,6 @@ services:
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
volumes:
|
||||
@@ -35,15 +33,13 @@ services:
|
||||
- postgres
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
# ports:
|
||||
# - 27017:27017
|
||||
environment:
|
||||
- FERRETDB_POSTGRESQL_URL=postgres://postgres:5432/${KOMODO_DATABASE_DB_NAME:-komodo}
|
||||
|
||||
core:
|
||||
image: ghcr.io/moghtech/komodo:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
@@ -51,8 +47,6 @@ services:
|
||||
- ferretdb
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
ports:
|
||||
- 9120:9120
|
||||
env_file: ./compose.env
|
||||
@@ -74,20 +68,13 @@ services:
|
||||
## or deploy the Periphery binary with systemd using
|
||||
## https://github.com/moghtech/komodo/tree/main/scripts
|
||||
periphery:
|
||||
image: ghcr.io/moghtech/periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
env_file: ./compose.env
|
||||
environment:
|
||||
PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos
|
||||
PERIPHERY_STACK_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/stacks
|
||||
PERIPHERY_SSL_KEY_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/key.pem
|
||||
PERIPHERY_SSL_CERT_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/cert.pem
|
||||
volumes:
|
||||
## Mount external docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -103,7 +90,4 @@ volumes:
|
||||
# Postgres
|
||||
pg-data:
|
||||
# Core
|
||||
repo-cache:
|
||||
|
||||
networks:
|
||||
default: {}
|
||||
repo-cache:
|
||||
@@ -15,8 +15,6 @@ services:
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
# ports:
|
||||
# - 27017:27017
|
||||
volumes:
|
||||
@@ -25,7 +23,7 @@ services:
|
||||
- FERRETDB_HANDLER=sqlite
|
||||
|
||||
core:
|
||||
image: ghcr.io/moghtech/komodo:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
@@ -33,8 +31,6 @@ services:
|
||||
- ferretdb
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
ports:
|
||||
- 9120:9120
|
||||
env_file: ./compose.env
|
||||
@@ -56,20 +52,13 @@ services:
|
||||
## or deploy the Periphery binary with systemd using
|
||||
## https://github.com/moghtech/komodo/tree/main/scripts
|
||||
periphery:
|
||||
image: ghcr.io/moghtech/periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: ${COMPOSE_LOGGING_DRIVER:-local}
|
||||
networks:
|
||||
- default
|
||||
env_file: ./compose.env
|
||||
environment:
|
||||
PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos
|
||||
PERIPHERY_STACK_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/stacks
|
||||
PERIPHERY_SSL_KEY_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/key.pem
|
||||
PERIPHERY_SSL_CERT_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/cert.pem
|
||||
volumes:
|
||||
## Mount external docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -85,7 +74,4 @@ volumes:
|
||||
# Sqlite
|
||||
sqlite-data:
|
||||
# Core
|
||||
repo-cache:
|
||||
|
||||
networks:
|
||||
default: {}
|
||||
repo-cache:
|
||||
@@ -33,6 +33,12 @@ host = "https://demo.komo.do"
|
||||
## Default: 9120
|
||||
port = 9120
|
||||
|
||||
## The IP address the core server will bind to.
|
||||
## The default will allow it to accept external IPv4 and IPv6 connections.
|
||||
## Env: KOMODO_BIND_IP
|
||||
## Default: [::]
|
||||
bind_ip = "[::]"
|
||||
|
||||
## This is the token used to authenticate core requests to periphery.
|
||||
## Ensure this matches a passkey in the connected periphery configs.
|
||||
## If the periphery servers don't have passkeys configured, this doesn't need to be changed.
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
## Default: 8120
|
||||
port = 8120
|
||||
|
||||
## The IP address the periphery server will bind to.
|
||||
## The default will allow it to accept external IPv4 and IPv6 connections.
|
||||
## Env: PERIPHERY_BIND_IP
|
||||
## Default: [::]
|
||||
bind_ip = "[::]"
|
||||
|
||||
## The directory periphery will use to manage repos.
|
||||
## The periphery user must have write access to this directory.
|
||||
## Env: PERIPHERY_REPO_DIR
|
||||
@@ -30,6 +36,12 @@ repo_dir = "/etc/komodo/repos"
|
||||
## Default: /etc/komodo/stacks
|
||||
stack_dir = "/etc/komodo/stacks"
|
||||
|
||||
## The directory periphery will use to manage builds.
|
||||
## The periphery user must have write access to this directory.
|
||||
## Env: PERIPHERY_BUILD_DIR
|
||||
## Default: /etc/komodo/builds
|
||||
build_dir = "/etc/komodo/builds"
|
||||
|
||||
## How often Periphery polls the host for system stats,
|
||||
## like CPU / memory usage.
|
||||
## Env: PERIPHERY_STATS_POLLING_RATE
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Connect More Servers
|
||||
|
||||
```mdx-code-block
|
||||
import RemoteCodeFile from "@site/src/components/RemoteCodeFile";
|
||||
```
|
||||
|
||||
Connecting a server to Komodo has 2 steps:
|
||||
|
||||
1. Install the Periphery agent on the server (either binary or container).
|
||||
@@ -13,7 +17,7 @@ You can install Periphery as a systemd managed process, run it as a [docker cont
|
||||
Allowing unintended access to the Periphery agent API is a security risk.
|
||||
Ensure to take appropriate measures to block access to the Periphery API, such as firewall rules on port `8120`.
|
||||
Additionally, you can whitelist your Komodo Core IP address in the [Periphery config](https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml#L46),
|
||||
and configure it to [only accept requests matching including your Core passkey](https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml#L51).
|
||||
and configure it to [only accept requests including your Core passkey](https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml#L51).
|
||||
:::
|
||||
|
||||
### Install the Periphery agent - systemd
|
||||
@@ -37,53 +41,14 @@ This script can be run multiple times without issue, and it won't change existin
|
||||
|
||||
### Install the Periphery agent - container
|
||||
|
||||
You can use a docker compose file like this:
|
||||
```yaml
|
||||
services:
|
||||
periphery:
|
||||
image: ghcr.io/mbecker20/periphery:latest
|
||||
# image: ghcr.io/mbecker20/periphery:latest-aarch64 # use for arm support
|
||||
labels:
|
||||
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
|
||||
logging:
|
||||
driver: local
|
||||
ports:
|
||||
- 8120:8120
|
||||
volumes:
|
||||
## Mount external docker socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
## Allow Periphery to see processes outside of container
|
||||
- /proc:/proc
|
||||
## use self signed certs in docker volume,
|
||||
## or mount your own signed certs.
|
||||
- ssl-certs:/etc/komodo/ssl
|
||||
## manage repos in a docker volume,
|
||||
## or change it to an accessible host directory.
|
||||
- repos:/etc/komodo/repos
|
||||
## manage stack files in a docker volume,
|
||||
## or change it to an accessible host directory.
|
||||
- stacks:/etc/komodo/stacks
|
||||
## Optionally mount a path to store compose files
|
||||
# - /path/to/compose:/host/compose
|
||||
environment:
|
||||
## Full list: `https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml`
|
||||
## Configure the same passkey given to Komodo Core (KOMODO_PASSKEY)
|
||||
PERIPHERY_PASSKEYS: your_core_passkey # Alt: PERIPHERY_PASSKEYS_FILE
|
||||
## Adding IP here will ensure calling IP is in the list. (optional)
|
||||
PERIPHERY_ALLOWED_IPS:
|
||||
## Enable HTTPS server
|
||||
PERIPHERY_SSL_ENABLED: true
|
||||
## If the disk size is overreporting, can use one of these to
|
||||
## whitelist / blacklist the disks to filter them, whichever is easier.
|
||||
## Accepts comma separated list of paths.
|
||||
## Usually whitelisting /etc/hostname gives correct size.
|
||||
PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname
|
||||
# PERIPHERY_EXCLUDE_DISK_MOUNTS: /snap,/etc/repos
|
||||
You can use a docker compose file:
|
||||
|
||||
volumes:
|
||||
ssl-certs:
|
||||
repos:
|
||||
stacks:
|
||||
```mdx-code-block
|
||||
<RemoteCodeFile
|
||||
title="https://github.com/moghtech/komodo/blob/main/compose/periphery.compose.yaml"
|
||||
url="https://raw.githubusercontent.com/moghtech/komodo/main/compose/periphery.compose.yaml"
|
||||
language="yaml"
|
||||
/>
|
||||
```
|
||||
|
||||
### Manual install steps - binaries
|
||||
@@ -112,7 +77,7 @@ Ensure that the user which periphery is run as has access to the docker group wi
|
||||
```
|
||||
periphery \
|
||||
--config-path /path/to/periphery.config.base.toml \
|
||||
--config-path /other_path/to/overide-periphery-config-directory \
|
||||
--config-path /other_path/to/override-periphery-config-directory \
|
||||
--config-keyword periphery \
|
||||
--config-keyword config \
|
||||
--merge-nested-config true
|
||||
@@ -122,21 +87,26 @@ periphery \
|
||||
You can run `periphery --help` to see the manual.
|
||||
:::
|
||||
|
||||
When running periphery in docker, use [command](https://docs.docker.com/reference/compose-file/services/#command) to pass in additional arguments.
|
||||
```
|
||||
command: periphery --config-path /path/in/container/to/periphery.config.base.toml
|
||||
```
|
||||
|
||||
### Passing config files
|
||||
|
||||
Either file paths or directory paths can be passed to `--config-path`.
|
||||
|
||||
When using directories, the file entries can be filtered by name with the `--config-keyword` argument, which can be passed multiple times to add more keywords. If passed, then only config files with file names that contain all keywords will be merged.
|
||||
|
||||
When passing multiple config files, later --config-path given in the command will always overide previous ones. Directory config files are merged in alphabetical order by name, so `config_b.toml` will overide `config_a.toml`.
|
||||
When passing multiple config files, later --config-path given in the command will always override previous ones. Directory config files are merged in alphabetical order by name, so `config_b.toml` will override `config_a.toml`.
|
||||
|
||||
There are two ways to merge config files. The default behavior is to completely replace any base fields with whatever fields are present in the overide config. So if you pass `allowed_ips = []` in your overide config, the final allowed_ips will be an empty list as well.
|
||||
There are two ways to merge config files. The default behavior is to completely replace any base fields with whatever fields are present in the override config. So if you pass `allowed_ips = []` in your override config, the final allowed_ips will be an empty list as well.
|
||||
|
||||
`--merge-nested-config true` will merge config fields recursively and extend config array fields.
|
||||
|
||||
For example, with `--merge-nested-config true` you can specify an allowed ip in the base config, and another in the overide config, they will both be present in the final config.
|
||||
For example, with `--merge-nested-config true` you can specify an allowed ip in the base config, and another in the override config, they will both be present in the final config.
|
||||
|
||||
Similarly, you can specify a base docker / github account pair, and extend them with additional accounts in the overide config.
|
||||
Similarly, you can specify a base docker / github account pair, and extend them with additional accounts in the override config.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -146,8 +116,6 @@ wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/config/per
|
||||
```
|
||||
|
||||
```mdx-code-block
|
||||
import RemoteCodeFile from "@site/src/components/RemoteCodeFile";
|
||||
|
||||
<RemoteCodeFile
|
||||
title="https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml"
|
||||
url="https://raw.githubusercontent.com/moghtech/komodo/main/config/periphery.config.toml"
|
||||
|
||||
@@ -39,7 +39,7 @@ Komodo Periphery is a small stateless web server that runs on all connected serv
|
||||
|
||||
## Core API
|
||||
|
||||
Komodo exposes powerful functionality over the Core's REST and Websocket API, enabling infrastructure engineers to manage their infrastructure programmatically. There is a [rust crate](https://crates.io/crates/komodo_client) to simplify programmatic interaction with the API, but in general this can be accomplished using any programming language that can make REST requests.
|
||||
Komodo exposes powerful functionality over the Core's REST and Websocket API, enabling infrastructure engineers to manage their infrastructure programmatically. There is a [rust crate](https://crates.io/crates/komodo_client) and [npm package](https://www.npmjs.com/package/komodo_client) to simplify programmatic interaction with the API, but in general this can be accomplished using any programming language that can make REST requests.
|
||||
|
||||
## Permissioning
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Other Resources
|
||||
|
||||
### Posts and Guides
|
||||
- [Migrating to Komodo](https://blog.foxxmd.dev/posts/migrating-to-komodo) by [FoxxMD](https://github.com/FoxxMD)
|
||||
- [FAQ, Tips, and Tricks](https://blog.foxxmd.dev/posts/komodo-tips-tricks) by [FoxxMD](https://github.com/FoxxMD)
|
||||
- [Compose Environments Explained](https://blog.foxxmd.dev/posts/compose-envs-explained) by [FoxxMD](https://github.com/FoxxMD)
|
||||
- [How To: Automate version updates for your self-hosted Docker containers with Gitea, Renovate, and Komodo](https://nickcunningh.am/blog/how-to-automate-version-updates-for-your-self-hosted-docker-containers-with-gitea-renovate-and-komodo) by [TheNickOfTime](https://github.com/TheNickOfTime)
|
||||
|
||||
### Community Alerters
|
||||
These provide alerting implementations which can be used with the `Custom` Alerter type.
|
||||
- [Discord](https://github.com/FoxxMD/deploy-discord-alerter) by [FoxxMD](https://github.com/FoxxMD)
|
||||
- [Telegram](https://github.com/mattsmallman/komodo-alert-to-telgram) by [mattsmallman](https://github.com/mattsmallman)
|
||||
- [Ntfy](https://github.com/FoxxMD/deploy-ntfy-alerter) by [FoxxMD](https://github.com/FoxxMD)
|
||||
- [Gotify](https://github.com/FoxxMD/deploy-gotify-alerter) by [FoxxMD](https://github.com/FoxxMD)
|
||||
- [Apprise](https://github.com/FoxxMD/deploy-apprise-alerter) by [FoxxMD](https://github.com/FoxxMD)
|
||||
@@ -1,7 +1,7 @@
|
||||
# Sqlite
|
||||
|
||||
:::info
|
||||
FerritDB + Sqlite authentication support is [still experimental](https://docs.ferretdb.io/security/authentication/#experimental-authentication-mode),
|
||||
FerretDB + Sqlite authentication support is [still experimental](https://docs.ferretdb.io/security/authentication/#experimental-authentication-mode),
|
||||
and is not enabled in this database configuration. Because of this, the values for `DB_USERNAME` and `DB_PASSWORD`
|
||||
are ignored when using Sqlite, and the database port is not exposed publicly by default.
|
||||
:::
|
||||
@@ -22,4 +22,4 @@ docker compose -p komodo -f komodo/sqlite.compose.yaml --env-file komodo/compose
|
||||
import ComposeAndEnv from "@site/src/components/ComposeAndEnv";
|
||||
|
||||
<ComposeAndEnv file_name="sqlite.compose.yaml" />
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
# Sync Resources
|
||||
|
||||
Komodo is able to create, update, delete, and deploy resources declared in TOML files by diffing them against the existing resources,
|
||||
and apply updates based on the diffs. Push the files to a remote git repo and create a `ResourceSync` pointing to the repo,
|
||||
and the core backend will poll for any updates (you can also manually trigger an update poll / execution in the UI).
|
||||
and apply updates based on the diffs. Similar to Stacks, the files can be configured in UI, in a local file, or in files pushed to a remote git repo.
|
||||
The Komodo Core backend will poll the files for for any updates, and alert about pending changes when diffs are detected.
|
||||
|
||||
File detection is additive and recursive, so you can spread out your resource declarations across any number of files
|
||||
and use any nesting of folders to organize resources inside a root folder. Additionally, you can create multiple `ResourceSyncs`
|
||||
You can spread out your resource declarations across any number of files
|
||||
and use any nesting of folders to organize resources inside a root folder.
|
||||
Additionally, you can create multiple `ResourceSyncs` and configure `Match Tags` to filter down which resources are synced,
|
||||
and each sync will be handled independently. This allows different syncs to manage resources on a "per-project" basis.
|
||||
|
||||
The UI will display the computed sync actions and only execute them upon manual confirmation.
|
||||
Or the sync execution git webhook may be configured on the git repo to
|
||||
automatically execute syncs upon pushes to the configured branch.
|
||||
|
||||
## Commit to Syncs
|
||||
|
||||
If the Sync is pointing to just a single file, you can enable "Managed Mode" to allow Core to write the updates you made in UI _back to the file_.
|
||||
This works no matter where the files are located, and will create a commit to your git repository for repo based files.
|
||||
|
||||
## Example Declarations
|
||||
|
||||
### Server
|
||||
|
||||
+2
-1
@@ -65,7 +65,8 @@ const sidebars: SidebarsConfig = {
|
||||
"webhooks",
|
||||
"version-upgrades",
|
||||
"api",
|
||||
"development"
|
||||
"development",
|
||||
"other-resources"
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
+9001
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM node:20.12-alpine as builder
|
||||
FROM node:20.12-alpine AS builder
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
@@ -7,7 +7,7 @@ COPY ./client/core/ts ./client
|
||||
|
||||
# Optionally specify a specific Komodo host.
|
||||
ARG VITE_KOMODO_HOST=""
|
||||
ENV VITE_KOMODO_HOST=${VITE_KOMODO_HOST}
|
||||
ENV VITE_KOMODO_HOST=$VITE_KOMODO_HOST
|
||||
|
||||
# Build and link the client
|
||||
RUN cd client && yarn && yarn build && yarn link
|
||||
|
||||
@@ -117,6 +117,8 @@ export function KomodoClient(url, options) {
|
||||
// Sleep for a bit before checking for websocket closed
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
// Sleep for a bit before retrying to avoid spam.
|
||||
await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms));
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
|
||||
+2
@@ -46,6 +46,7 @@ export type ReadResponses = {
|
||||
GetServer: Types.GetServerResponse;
|
||||
GetServerState: Types.GetServerStateResponse;
|
||||
GetPeripheryVersion: Types.GetPeripheryVersionResponse;
|
||||
GetDockerContainersSummary: Types.GetDockerContainersSummaryResponse;
|
||||
ListDockerContainers: Types.ListDockerContainersResponse;
|
||||
ListAllDockerContainers: Types.ListAllDockerContainersResponse;
|
||||
InspectDockerContainer: Types.InspectDockerContainerResponse;
|
||||
@@ -168,6 +169,7 @@ export type WriteResponses = {
|
||||
DeleteBuild: Types.Build;
|
||||
UpdateBuild: Types.Build;
|
||||
RenameBuild: Types.Update;
|
||||
WriteBuildFileContents: Types.Update;
|
||||
RefreshBuildCache: Types.NoData;
|
||||
CreateBuildWebhook: Types.CreateBuildWebhookResponse;
|
||||
DeleteBuildWebhook: Types.DeleteBuildWebhookResponse;
|
||||
|
||||
Vendored
+126
-41
@@ -43,7 +43,10 @@ export interface Resource<Config, Info> {
|
||||
base_permission?: PermissionLevel;
|
||||
}
|
||||
export interface ActionConfig {
|
||||
/** Typescript file contents using pre-initialized `komodo` client. */
|
||||
/**
|
||||
* Typescript file contents using pre-initialized `komodo` client.
|
||||
* Supports variable / secret interpolation.
|
||||
*/
|
||||
file_contents?: string;
|
||||
/** Whether incoming webhooks actually trigger action. */
|
||||
webhook_enabled: boolean;
|
||||
@@ -119,6 +122,11 @@ export type AlerterEndpoint =
|
||||
| {
|
||||
type: "Discord";
|
||||
params: DiscordAlerterEndpoint;
|
||||
}
|
||||
/** Send alert to Ntfy */
|
||||
| {
|
||||
type: "Ntfy";
|
||||
params: NtfyAlerterEndpoint;
|
||||
};
|
||||
/** Used to reference a specific resource across all resource types */
|
||||
export type ResourceTarget = {
|
||||
@@ -216,10 +224,6 @@ export interface Version {
|
||||
minor: number;
|
||||
patch: number;
|
||||
}
|
||||
export interface SystemCommand {
|
||||
path?: string;
|
||||
command?: string;
|
||||
}
|
||||
/** Configuration for an image registry */
|
||||
export interface ImageRegistryConfig {
|
||||
/**
|
||||
@@ -235,6 +239,10 @@ export interface ImageRegistryConfig {
|
||||
*/
|
||||
organization?: string;
|
||||
}
|
||||
export interface SystemCommand {
|
||||
path?: string;
|
||||
command?: string;
|
||||
}
|
||||
/** The build configuration. */
|
||||
export interface BuildConfig {
|
||||
/** Which builder is used to build the image. */
|
||||
@@ -256,8 +264,8 @@ export interface BuildConfig {
|
||||
*/
|
||||
image_name?: string;
|
||||
/**
|
||||
* An extra tag put before the build version, for the image pushed to the repository.
|
||||
* Eg. in image tag of `aarch64` would push to moghtech/komodo:1.13.2-aarch64.
|
||||
* An extra tag put after the build version, for the image pushed to the repository.
|
||||
* Eg. in image tag of `aarch64` would push to moghtech/komodo-core:1.13.2-aarch64.
|
||||
* If this is empty, the image tag will just be the build version.
|
||||
*
|
||||
* Can be used in conjunction with `image_name` to direct multiple builds
|
||||
@@ -296,8 +304,13 @@ export interface BuildConfig {
|
||||
* If its an empty string, use the default secret from the config.
|
||||
*/
|
||||
webhook_secret?: string;
|
||||
/** The optional command run after repo clone and before docker build. */
|
||||
pre_build?: SystemCommand;
|
||||
/**
|
||||
* If this is checked, the build will source the files on the host.
|
||||
* Use `build_path` and `dockerfile_path` to specify the path on the host.
|
||||
* This is useful for those who wish to setup their files on the host,
|
||||
* rather than defining the contents in UI or in a git repo.
|
||||
*/
|
||||
files_on_host?: boolean;
|
||||
/**
|
||||
* The path of the docker build context relative to the root of the repo.
|
||||
* Default: "." (the root of the repo).
|
||||
@@ -313,6 +326,13 @@ export interface BuildConfig {
|
||||
use_buildx?: boolean;
|
||||
/** Any extra docker cli arguments to be included in the build command */
|
||||
extra_args?: string[];
|
||||
/** The optional command run after repo clone and before docker build. */
|
||||
pre_build?: SystemCommand;
|
||||
/**
|
||||
* UI defined dockerfile contents.
|
||||
* Supports variable / secret interpolation.
|
||||
*/
|
||||
dockerfile?: string;
|
||||
/**
|
||||
* Docker build arguments.
|
||||
*
|
||||
@@ -336,11 +356,27 @@ export interface BuildConfig {
|
||||
labels?: string;
|
||||
}
|
||||
export interface BuildInfo {
|
||||
/** The timestamp build was last built. */
|
||||
last_built_at: I64;
|
||||
/** Latest built short commit hash, or null. */
|
||||
built_hash?: string;
|
||||
/** Latest built commit message, or null. Only for repo based stacks */
|
||||
built_message?: string;
|
||||
/**
|
||||
* The last built dockerfile contents.
|
||||
* This is updated whenever Komodo successfully runs the build.
|
||||
*/
|
||||
built_contents?: string;
|
||||
/** The absolute path to the file */
|
||||
remote_path?: string;
|
||||
/**
|
||||
* The remote dockerfile contents, whether on host or in repo.
|
||||
* This is updated whenever Komodo refreshes the build cache.
|
||||
* It will be empty if the dockerfile is defined directly in the build config.
|
||||
*/
|
||||
remote_contents?: string;
|
||||
/** If there was an error in getting the remote contents, it will be here. */
|
||||
remote_error?: string;
|
||||
/** Latest remote short commit hash, or null. */
|
||||
latest_hash?: string;
|
||||
/** Latest remote commit message, or null */
|
||||
@@ -358,26 +394,28 @@ export declare enum BuildState {
|
||||
Unknown = "Unknown"
|
||||
}
|
||||
export interface BuildListItemInfo {
|
||||
/** State of the build. Reflects whether most recent build successful. */
|
||||
state: BuildState;
|
||||
/** Unix timestamp in milliseconds of last build */
|
||||
last_built_at: I64;
|
||||
/** The current version of the build */
|
||||
version: Version;
|
||||
/** The builder attached to build. */
|
||||
builder_id: string;
|
||||
/** Whether build is in files on host mode. */
|
||||
files_on_host: boolean;
|
||||
/** The git provider domain */
|
||||
git_provider: string;
|
||||
/** The image registry domain */
|
||||
image_registry_domain: string;
|
||||
git_provider?: string;
|
||||
/** The repo used as the source of the build */
|
||||
repo: string;
|
||||
repo?: string;
|
||||
/** The branch of the repo */
|
||||
branch: string;
|
||||
/** State of the build. Reflects whether most recent build successful. */
|
||||
state: BuildState;
|
||||
branch?: string;
|
||||
/** Latest built short commit hash, or null. */
|
||||
built_hash?: string;
|
||||
/** Latest short commit hash, or null. Only for repo based stacks */
|
||||
latest_hash?: string;
|
||||
/** The image registry domain */
|
||||
image_registry_domain?: string;
|
||||
}
|
||||
export type BuildListItem = ResourceListItem<BuildListItemInfo>;
|
||||
export interface BuildQuerySpecifics {
|
||||
@@ -1622,6 +1660,12 @@ export interface ServerConfig {
|
||||
* default: 2
|
||||
*/
|
||||
timeout_seconds: I64;
|
||||
/**
|
||||
* An optional override passkey to use
|
||||
* to authenticate with periphery agent.
|
||||
* If this is empty, will use passkey in core config.
|
||||
*/
|
||||
passkey?: string;
|
||||
/**
|
||||
* Sometimes the system stats reports a mount path that is not desired.
|
||||
* Use this field to filter it out from the report.
|
||||
@@ -1732,31 +1776,6 @@ export interface StackConfig {
|
||||
destroy_before_deploy?: boolean;
|
||||
/** Whether to skip secret interpolation into the stack environment variables. */
|
||||
skip_secret_interp?: boolean;
|
||||
/**
|
||||
* If this is checked, the stack will source the files on the host.
|
||||
* Use `run_directory` and `file_paths` to specify the path on the host.
|
||||
* This is useful for those who wish to setup their files on the host using SSH or similar,
|
||||
* rather than defining the contents in UI or in a git repo.
|
||||
*/
|
||||
files_on_host?: boolean;
|
||||
/** Directory to change to (`cd`) before running `docker compose up -d`. */
|
||||
run_directory?: string;
|
||||
/**
|
||||
* Add paths to compose files, relative to the run path.
|
||||
* If this is empty, will use file `compose.yaml`.
|
||||
*/
|
||||
file_paths?: string[];
|
||||
/**
|
||||
* The name of the written environment file before `docker compose up`.
|
||||
* Relative to the run directory root.
|
||||
* Default: .env
|
||||
*/
|
||||
env_file_path: string;
|
||||
/**
|
||||
* Add additional env files to attach with `--env-file`.
|
||||
* Relative to the run directory root.
|
||||
*/
|
||||
additional_env_files?: string[];
|
||||
/** The git provider domain. Default: github.com */
|
||||
git_provider: string;
|
||||
/**
|
||||
@@ -1796,6 +1815,31 @@ export interface StackConfig {
|
||||
* If this option is enabled, will always run `DeployStack` without diffing.
|
||||
*/
|
||||
webhook_force_deploy?: boolean;
|
||||
/**
|
||||
* If this is checked, the stack will source the files on the host.
|
||||
* Use `run_directory` and `file_paths` to specify the path on the host.
|
||||
* This is useful for those who wish to setup their files on the host,
|
||||
* rather than defining the contents in UI or in a git repo.
|
||||
*/
|
||||
files_on_host?: boolean;
|
||||
/** Directory to change to (`cd`) before running `docker compose up -d`. */
|
||||
run_directory?: string;
|
||||
/**
|
||||
* Add paths to compose files, relative to the run path.
|
||||
* If this is empty, will use file `compose.yaml`.
|
||||
*/
|
||||
file_paths?: string[];
|
||||
/**
|
||||
* The name of the written environment file before `docker compose up`.
|
||||
* Relative to the run directory root.
|
||||
* Default: .env
|
||||
*/
|
||||
env_file_path: string;
|
||||
/**
|
||||
* Add additional env files to attach with `--env-file`.
|
||||
* Relative to the run directory root.
|
||||
*/
|
||||
additional_env_files?: string[];
|
||||
/** Whether to send StackStateChange alerts for this stack. */
|
||||
send_alerts: boolean;
|
||||
/** Used with `registry_account` to login to a registry before docker compose up. */
|
||||
@@ -1827,6 +1871,7 @@ export interface StackConfig {
|
||||
* The contents of the file directly, for management in the UI.
|
||||
* If this is empty, it will fall back to checking git config for
|
||||
* repo based compose file.
|
||||
* Supports variable / secret interpolation.
|
||||
*/
|
||||
file_contents?: string;
|
||||
/**
|
||||
@@ -2137,6 +2182,7 @@ export declare enum Operation {
|
||||
DeleteBuild = "DeleteBuild",
|
||||
RunBuild = "RunBuild",
|
||||
CancelBuild = "CancelBuild",
|
||||
WriteDockerfile = "WriteDockerfile",
|
||||
CreateRepo = "CreateRepo",
|
||||
UpdateRepo = "UpdateRepo",
|
||||
RenameRepo = "RenameRepo",
|
||||
@@ -3869,6 +3915,8 @@ export interface CloneArgs {
|
||||
branch: string;
|
||||
/** Specific commit hash. Optional */
|
||||
commit?: string;
|
||||
/** Use PERIPHERY_BUILD_DIR as the parent folder for the clone. */
|
||||
is_build: boolean;
|
||||
/** The clone destination path */
|
||||
destination?: string;
|
||||
/** Command to run after the repo has been cloned */
|
||||
@@ -4925,6 +4973,25 @@ export interface GetDeploymentsSummaryResponse {
|
||||
/** The number of Deployments with Unknown state */
|
||||
unknown: I64;
|
||||
}
|
||||
/**
|
||||
* Gets a summary of data relating to all containers.
|
||||
* Response: [GetDockerContainersSummaryResponse].
|
||||
*/
|
||||
export interface GetDockerContainersSummary {
|
||||
}
|
||||
/** Response for [GetDockerContainersSummary] */
|
||||
export interface GetDockerContainersSummaryResponse {
|
||||
/** The total number of Containers */
|
||||
total: number;
|
||||
/** The number of Containers with Running state */
|
||||
running: number;
|
||||
/** The number of Containers with Stopped or Paused or Created state */
|
||||
stopped: number;
|
||||
/** The number of Containers with Restarting or Dead state */
|
||||
unhealthy: number;
|
||||
/** The number of Containers with Unknown state */
|
||||
unknown: number;
|
||||
}
|
||||
/**
|
||||
* Get a specific docker registry account.
|
||||
* Response: [GetDockerRegistryAccountResponse].
|
||||
@@ -5988,6 +6055,11 @@ export interface NameAndId {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
/** Configuration for a Ntfy alerter. */
|
||||
export interface NtfyAlerterEndpoint {
|
||||
/** The ntfy topic URL */
|
||||
url: string;
|
||||
}
|
||||
/** Pauses all containers on the target server. Response: [Update] */
|
||||
export interface PauseAllContainers {
|
||||
/** Name or id */
|
||||
@@ -7031,6 +7103,13 @@ export interface UrlBuilderConfig {
|
||||
/** A custom passkey to use. Otherwise, use the default passkey. */
|
||||
passkey?: string;
|
||||
}
|
||||
/** Update dockerfile contents in Files on Server or Git Repo mode. Response: [Update]. */
|
||||
export interface WriteBuildFileContents {
|
||||
/** The name or id of the target Build. */
|
||||
build: string;
|
||||
/** The dockerfile contents to write. */
|
||||
contents: string;
|
||||
}
|
||||
/** Update file contents in Files on Server or Git Repo mode. Response: [Update]. */
|
||||
export interface WriteStackFileContents {
|
||||
/** The name or id of the target Stack. */
|
||||
@@ -7401,6 +7480,9 @@ export type ReadRequest = {
|
||||
} | {
|
||||
type: "InspectDockerVolume";
|
||||
params: InspectDockerVolume;
|
||||
} | {
|
||||
type: "GetDockerContainersSummary";
|
||||
params: GetDockerContainersSummary;
|
||||
} | {
|
||||
type: "ListAllDockerContainers";
|
||||
params: ListAllDockerContainers;
|
||||
@@ -7736,6 +7818,9 @@ export type WriteRequest = {
|
||||
} | {
|
||||
type: "RenameBuild";
|
||||
params: RenameBuild;
|
||||
} | {
|
||||
type: "WriteBuildFileContents";
|
||||
params: WriteBuildFileContents;
|
||||
} | {
|
||||
type: "RefreshBuildCache";
|
||||
params: RefreshBuildCache;
|
||||
|
||||
@@ -236,6 +236,7 @@ export var Operation;
|
||||
Operation["DeleteBuild"] = "DeleteBuild";
|
||||
Operation["RunBuild"] = "RunBuild";
|
||||
Operation["CancelBuild"] = "CancelBuild";
|
||||
Operation["WriteDockerfile"] = "WriteDockerfile";
|
||||
Operation["CreateRepo"] = "CreateRepo";
|
||||
Operation["UpdateRepo"] = "UpdateRepo";
|
||||
Operation["RenameRepo"] = "RenameRepo";
|
||||
|
||||
@@ -105,7 +105,7 @@ export const ActionConfig = ({ id }: { id: string }) => {
|
||||
},
|
||||
{
|
||||
label: "Webhook",
|
||||
description: `Configure your ${webhook_integration}-style repo provider to send webhooks to Komodo`,
|
||||
description: `Copy the webhook given here, and configure your ${webhook_integration}-style repo provider to send webhooks to Komodo`,
|
||||
components: {
|
||||
["Builder" as any]: () => (
|
||||
<WebhookBuilder git_provider={ACTION_GIT_PROVIDER}>
|
||||
|
||||
@@ -13,6 +13,7 @@ const ENDPOINT_TYPES: Types.AlerterEndpoint["type"][] = [
|
||||
"Custom",
|
||||
"Discord",
|
||||
"Slack",
|
||||
"Ntfy",
|
||||
];
|
||||
|
||||
export const EndpointConfig = ({
|
||||
@@ -67,5 +68,7 @@ const default_url = (type: Types.AlerterEndpoint["type"]) => {
|
||||
? "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
: type === "Discord"
|
||||
? "https://discord.com/api/webhooks/XXXXXXXXXXXX/XXXX-XXXXXXXXXX"
|
||||
: type === "Ntfy"
|
||||
? "https://ntfy.sh/komodo"
|
||||
: "";
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,11 @@
|
||||
import { Section } from "@components/layouts";
|
||||
import { useInvalidate, useRead, useUser, useWrite } from "@lib/hooks";
|
||||
import {
|
||||
useInvalidate,
|
||||
useLocalStorage,
|
||||
useRead,
|
||||
useUser,
|
||||
useWrite,
|
||||
} from "@lib/hooks";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Factory, FolderGit, Hammer, Loader2, RefreshCcw } from "lucide-react";
|
||||
import { BuildConfig } from "./config";
|
||||
@@ -13,7 +19,6 @@ import {
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { cn } from "@lib/utils";
|
||||
import { useState } from "react";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
|
||||
import { ResourceComponents } from "..";
|
||||
import { Types } from "komodo_client";
|
||||
@@ -27,6 +32,7 @@ import { useBuilder } from "../builder";
|
||||
import { RenameResource } from "@components/config/util";
|
||||
import { GroupActions } from "@components/group-actions";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
|
||||
import { BuildInfo } from "./info";
|
||||
|
||||
export const useBuild = (id?: string) =>
|
||||
useRead("ListBuilds", {}, { refetchInterval: 10_000 }).data?.find(
|
||||
@@ -42,8 +48,11 @@ const BuildIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
return <Hammer className={cn(`w-${size} h-${size}`, state && color)} />;
|
||||
};
|
||||
|
||||
const ConfigOrDeployments = ({ id }: { id: string }) => {
|
||||
const [view, setView] = useState("Config");
|
||||
const ConfigInfoDeployments = ({ id }: { id: string }) => {
|
||||
const [view, setView] = useLocalStorage<"Config" | "Info" | "Deployments">(
|
||||
"build-tabs-v1",
|
||||
"Config"
|
||||
);
|
||||
const deployments = useRead("ListDeployments", {}).data?.filter(
|
||||
(deployment) => deployment.info.build_id === id
|
||||
);
|
||||
@@ -53,6 +62,9 @@ const ConfigOrDeployments = ({ id }: { id: string }) => {
|
||||
<TabsTrigger value="Config" className="w-[110px]">
|
||||
Config
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Info" className="w-[110px]">
|
||||
Info
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="Deployments"
|
||||
className="w-[110px]"
|
||||
@@ -64,13 +76,16 @@ const ConfigOrDeployments = ({ id }: { id: string }) => {
|
||||
);
|
||||
return (
|
||||
<Tabs
|
||||
value={deploymentsDisabled ? "Config" : view}
|
||||
onValueChange={setView}
|
||||
value={deploymentsDisabled && view === "Deployments" ? "Config" : view}
|
||||
onValueChange={setView as any}
|
||||
className="grid gap-4"
|
||||
>
|
||||
<TabsContent value="Config">
|
||||
<BuildConfig id={id} titleOther={titleOther} />
|
||||
</TabsContent>
|
||||
<TabsContent value="Info">
|
||||
<BuildInfo id={id} titleOther={titleOther} />
|
||||
</TabsContent>
|
||||
<TabsContent value="Deployments">
|
||||
<Section
|
||||
titleOther={titleOther}
|
||||
@@ -265,7 +280,7 @@ export const BuildComponents: RequiredResourceComponents = {
|
||||
|
||||
Page: {},
|
||||
|
||||
Config: ConfigOrDeployments,
|
||||
Config: ConfigInfoDeployments,
|
||||
|
||||
DangerZone: ({ id }) => (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
import { Section } from "@components/layouts";
|
||||
import { ReactNode, useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@ui/card";
|
||||
import { useFullBuild } from ".";
|
||||
import { cn, updateLogToHtml } from "@lib/utils";
|
||||
import { MonacoEditor } from "@components/monaco";
|
||||
import { useEditPermissions } from "@pages/resource";
|
||||
import { ConfirmUpdate } from "@components/config/util";
|
||||
import { useLocalStorage, useRead, useWrite } from "@lib/hooks";
|
||||
import { Button } from "@ui/button";
|
||||
import { Clock, FilePlus, History } from "lucide-react";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { ConfirmButton, ShowHideButton } from "@components/util";
|
||||
import { DEFAULT_BUILD_DOCKERFILE_CONTENTS } from "./config";
|
||||
import { fmt_duration } from "@lib/formatting";
|
||||
|
||||
export const BuildInfo = ({
|
||||
id,
|
||||
titleOther,
|
||||
}: {
|
||||
id: string;
|
||||
titleOther: ReactNode;
|
||||
}) => {
|
||||
const [edits, setEdits] = useLocalStorage<{ contents: string | undefined }>(
|
||||
`build-${id}-edits`,
|
||||
{ contents: undefined }
|
||||
);
|
||||
const [showContents, setShowContents] = useState(true);
|
||||
const { canWrite } = useEditPermissions({ type: "Build", id });
|
||||
const { toast } = useToast();
|
||||
const { mutateAsync, isPending } = useWrite("WriteBuildFileContents", {
|
||||
onSuccess: (res) => {
|
||||
toast({
|
||||
title: res.success ? "Contents written." : "Failed to write contents.",
|
||||
variant: res.success ? undefined : "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const build = useFullBuild(id);
|
||||
|
||||
const recent_builds = useRead("ListUpdates", {
|
||||
query: { "target.type": "Build", "target.id": id, operation: "RunBuild" },
|
||||
}).data;
|
||||
const _last_build = recent_builds?.updates[0];
|
||||
const last_build = useRead(
|
||||
"GetUpdate",
|
||||
{
|
||||
id: _last_build?.id!,
|
||||
},
|
||||
{ enabled: !!_last_build }
|
||||
).data;
|
||||
|
||||
const file_on_host = build?.config?.files_on_host ?? false;
|
||||
const git_repo = build?.config?.repo ? true : false;
|
||||
const canEdit = canWrite && (file_on_host || git_repo);
|
||||
|
||||
const remote_path = build?.info?.remote_path;
|
||||
const remote_contents = build?.info?.remote_contents;
|
||||
const remote_error = build?.info?.remote_error;
|
||||
|
||||
return (
|
||||
<Section titleOther={titleOther}>
|
||||
{/* Errors */}
|
||||
{remote_error && remote_error.length > 0 && (
|
||||
<Card className="flex flex-col gap-4">
|
||||
<CardHeader className="flex flex-row justify-between items-center pb-0">
|
||||
<div className="font-mono flex gap-2">
|
||||
{remote_path && (
|
||||
<>
|
||||
<div className="text-muted-foreground">Path:</div>
|
||||
{remote_path}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{canEdit && (
|
||||
<ConfirmButton
|
||||
title="Initialize File"
|
||||
icon={<FilePlus className="w-4 h-4" />}
|
||||
onClick={() => {
|
||||
if (build) {
|
||||
mutateAsync({
|
||||
build: build.name,
|
||||
contents: DEFAULT_BUILD_DOCKERFILE_CONTENTS,
|
||||
});
|
||||
}
|
||||
}}
|
||||
loading={isPending}
|
||||
/>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent className="pr-8">
|
||||
<pre
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: updateLogToHtml(remote_error),
|
||||
}}
|
||||
className="max-h-[500px] overflow-y-auto"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Update latest contents */}
|
||||
{remote_contents && remote_contents.length > 0 && (
|
||||
<Card className="flex flex-col gap-4">
|
||||
<CardHeader
|
||||
className={cn(
|
||||
"flex flex-row justify-between items-center",
|
||||
showContents && "pb-0"
|
||||
)}
|
||||
>
|
||||
{remote_path && (
|
||||
<CardTitle className="font-mono flex gap-2">
|
||||
<div className="text-muted-foreground">Path:</div>
|
||||
{remote_path}
|
||||
</CardTitle>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{canEdit && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setEdits({ contents: undefined })}
|
||||
className="flex items-center gap-2"
|
||||
disabled={!edits.contents}
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
Reset
|
||||
</Button>
|
||||
<ConfirmUpdate
|
||||
previous={{ contents: remote_contents }}
|
||||
content={{ contents: edits.contents }}
|
||||
onConfirm={async () => {
|
||||
if (build) {
|
||||
return await mutateAsync({
|
||||
build: build.name,
|
||||
contents: edits.contents!,
|
||||
}).then(() => setEdits({ contents: undefined }));
|
||||
}
|
||||
}}
|
||||
disabled={!edits.contents}
|
||||
language="dockerfile"
|
||||
loading={isPending}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<ShowHideButton show={showContents} setShow={setShowContents} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
{showContents && (
|
||||
<CardContent className="pr-8">
|
||||
<MonacoEditor
|
||||
value={edits.contents ?? remote_contents}
|
||||
language="dockerfile"
|
||||
readOnly={!canEdit}
|
||||
onValueChange={(contents) => setEdits({ contents })}
|
||||
/>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Last build output */}
|
||||
{last_build && last_build.logs.length > 0 && (
|
||||
<code className="font-bold">Last Build Logs</code>
|
||||
)}
|
||||
{last_build &&
|
||||
last_build.logs.length > 0 &&
|
||||
last_build.logs?.map((log, i) => (
|
||||
<Card key={i}>
|
||||
<CardHeader className="flex-col">
|
||||
<CardTitle>{log.stage}</CardTitle>
|
||||
<CardDescription className="flex gap-2">
|
||||
<span>
|
||||
Stage {i + 1} of {last_build.logs.length}
|
||||
</span>
|
||||
<span>|</span>
|
||||
<span className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4" />
|
||||
{fmt_duration(log.start_ts, log.end_ts)}
|
||||
</span>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-2">
|
||||
{log.command && (
|
||||
<div>
|
||||
<CardDescription>command</CardDescription>
|
||||
<pre className="max-h-[500px] overflow-y-auto">
|
||||
{log.command}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
{log.stdout && (
|
||||
<div>
|
||||
<CardDescription>stdout</CardDescription>
|
||||
<pre
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: updateLogToHtml(log.stdout),
|
||||
}}
|
||||
className="max-h-[500px] overflow-y-auto"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{log.stderr && (
|
||||
<div>
|
||||
<CardDescription>stderr</CardDescription>
|
||||
<pre
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: updateLogToHtml(log.stderr),
|
||||
}}
|
||||
className="max-h-[500px] overflow-y-auto"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -27,10 +27,19 @@ export const BuildTable = ({ builds }: { builds: Types.BuildListItem[] }) => {
|
||||
size: 200,
|
||||
},
|
||||
{
|
||||
accessorKey: "info.repo",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Repo" />
|
||||
),
|
||||
// header: ({ column }) => (
|
||||
// <SortableHeader column={column} title="Source" />
|
||||
// ),
|
||||
header: "Source",
|
||||
accessorFn: ({ info: { files_on_host, repo } }) => {
|
||||
if (files_on_host) {
|
||||
return "Files on Server";
|
||||
} else if (repo) {
|
||||
return repo;
|
||||
} else {
|
||||
return "UI Defined";
|
||||
}
|
||||
},
|
||||
size: 200,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -222,7 +222,9 @@ const ProcedureConfigInner = ({
|
||||
<CardHeader>
|
||||
<CardTitle>Webhook</CardTitle>
|
||||
<CardDescription>
|
||||
Trigger this Procedure with a webhook.
|
||||
Copy the webhook given here, and configure your
|
||||
{webhook_integration}-style repo provider to send webhooks to
|
||||
Komodo
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@@ -238,8 +238,7 @@ export const RepoConfig = ({ id }: { id: string }) => {
|
||||
},
|
||||
{
|
||||
label: "Webhooks",
|
||||
description:
|
||||
"Configure your repo provider to send webhooks to Komodo",
|
||||
description: `Copy the webhook given here, and configure your ${webhook_integration}-style repo provider to send webhooks to Komodo`,
|
||||
components: {
|
||||
["Guard" as any]: () => {
|
||||
if (update.branch ?? config.branch) {
|
||||
|
||||
@@ -90,7 +90,7 @@ export const ResourceSyncConfig = ({
|
||||
const disabled = global_disabled || perms !== Types.PermissionLevel.Write;
|
||||
|
||||
const git_provider = update.git_provider ?? config.git_provider;
|
||||
const integration = getWebhookIntegration(integrations, git_provider);
|
||||
const webhook_integration = getWebhookIntegration(integrations, git_provider);
|
||||
|
||||
const mode = getSyncMode(update, config);
|
||||
const managed = update.managed ?? config.managed ?? false;
|
||||
@@ -138,6 +138,7 @@ export const ResourceSyncConfig = ({
|
||||
<ConfigItem
|
||||
label="Choose Mode"
|
||||
description="Will the file contents be defined in UI, stored on the server, or pulled from a git repo?"
|
||||
boldLabel
|
||||
>
|
||||
<Select
|
||||
value={mode}
|
||||
@@ -331,8 +332,7 @@ export const ResourceSyncConfig = ({
|
||||
match_tags,
|
||||
{
|
||||
label: "Git Webhooks",
|
||||
description:
|
||||
"Configure your repo provider to send webhooks to Komodo",
|
||||
description: `Copy the webhook given here, and configure your ${webhook_integration}-style repo provider to send webhooks to Komodo`,
|
||||
contentHidden: !show.webhooks,
|
||||
actions: (
|
||||
<ShowHideButton
|
||||
@@ -360,7 +360,7 @@ export const ResourceSyncConfig = ({
|
||||
description="Trigger an update of the pending sync cache, to display the changes in the UI on push."
|
||||
>
|
||||
<CopyWebhook
|
||||
integration={integration}
|
||||
integration={webhook_integration}
|
||||
path={`/sync/${id_or_name === "Id" ? id : name}/refresh`}
|
||||
/>
|
||||
</ConfigItem>
|
||||
@@ -371,7 +371,7 @@ export const ResourceSyncConfig = ({
|
||||
description="Trigger an execution of the sync on push."
|
||||
>
|
||||
<CopyWebhook
|
||||
integration={integration}
|
||||
integration={webhook_integration}
|
||||
path={`/sync/${id_or_name === "Id" ? id : name}/sync`}
|
||||
/>
|
||||
</ConfigItem>
|
||||
|
||||
@@ -164,6 +164,7 @@ export const StackConfig = ({
|
||||
<ConfigItem
|
||||
label="Choose Mode"
|
||||
description="Will the file contents be defined in UI, stored on the server, or pulled from a git repo?"
|
||||
boldLabel
|
||||
>
|
||||
<Select
|
||||
value={mode}
|
||||
@@ -493,12 +494,10 @@ export const StackConfig = ({
|
||||
server_component,
|
||||
{
|
||||
label: "Files",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
run_directory: {
|
||||
label: "Run Directory",
|
||||
description:
|
||||
"Set the working directory when running the compose up command. Usually is the parent folder of the compose files.",
|
||||
description: `Set the working directory when running the 'compose up' command. Can be absolute path, or relative to $PERIPHERY_STACK_DIR/${stack.name}`,
|
||||
placeholder: "/path/to/folder",
|
||||
},
|
||||
file_paths: (value, set) => (
|
||||
@@ -583,13 +582,11 @@ export const StackConfig = ({
|
||||
},
|
||||
{
|
||||
label: "Files",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
run_directory: {
|
||||
label: "Run Directory",
|
||||
description:
|
||||
"Set the working directory when running the compose up command, relative to the stack root. ($periphery_stack_dir/$stack_name/$run_directory)",
|
||||
placeholder: "./path/to/folder",
|
||||
"Set the working directory when running the compose up command, relative to the root of the repo.",
|
||||
placeholder: "path/to/folder",
|
||||
},
|
||||
file_paths: (value, set) => (
|
||||
<ConfigList
|
||||
@@ -607,8 +604,7 @@ export const StackConfig = ({
|
||||
...general_common,
|
||||
{
|
||||
label: "Webhooks",
|
||||
description:
|
||||
"Configure your repo provider to send webhooks to Komodo",
|
||||
description: `Copy the webhook given here, and configure your ${webhook_integration}-style repo provider to send webhooks to Komodo`,
|
||||
actions: (
|
||||
<ShowHideButton
|
||||
show={show.webhooks}
|
||||
@@ -846,7 +842,7 @@ export const StackConfig = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const DEFAULT_STACK_FILE_CONTENTS = `## 🦎 Hello Komodo 🦎
|
||||
export const DEFAULT_STACK_FILE_CONTENTS = `## Add your compose file here
|
||||
services:
|
||||
hello_world:
|
||||
image: hello-world
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
tagsAtom,
|
||||
useTags,
|
||||
useInvalidate,
|
||||
useRead,
|
||||
useShiftKeyListener,
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
} from "@ui/command";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { useAtom } from "jotai";
|
||||
import { MinusCircle, PlusCircle, SearchX, Tag, X } from "lucide-react";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { tag_background_class } from "@lib/color";
|
||||
@@ -29,27 +28,24 @@ type TargetExcludingSystem = Exclude<Types.ResourceTarget, { type: "System" }>;
|
||||
export const TagsFilter = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [tags, setTags] = useAtom<string[]>(tagsAtom);
|
||||
const { tags, add_tag, remove_tag, clear_tags } = useTags();
|
||||
const all_tags = useRead("ListTags", {}).data;
|
||||
const filtered = filterBySplit(all_tags, search, (item) => item.name);
|
||||
useShiftKeyListener("T", () => setOpen(true));
|
||||
useShiftKeyListener("C", () => setTags([]));
|
||||
useShiftKeyListener("C", () => clear_tags());
|
||||
return (
|
||||
<div className="flex gap-3 items-center">
|
||||
{tags.length > 0 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="px-2 py-1.5 h-fit"
|
||||
onClick={() => setTags([])}
|
||||
onClick={() => clear_tags()}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<TagsFilterTags
|
||||
tag_ids={tags}
|
||||
onBadgeClick={(tag_id) => setTags(tags.filter((id) => id !== tag_id))}
|
||||
/>
|
||||
<TagsFilterTags tag_ids={tags} onBadgeClick={remove_tag} />
|
||||
|
||||
<Popover
|
||||
open={open}
|
||||
@@ -89,7 +85,7 @@ export const TagsFilter = () => {
|
||||
<CommandItem
|
||||
key={tag.name}
|
||||
onSelect={() => {
|
||||
setTags([...tags, tag._id!.$oid]);
|
||||
add_tag(tag._id!.$oid);
|
||||
setSearch("");
|
||||
setOpen(false);
|
||||
}}
|
||||
@@ -228,9 +224,10 @@ export const TagsWithBadge = ({
|
||||
};
|
||||
|
||||
export const TableTags = ({ tag_ids }: { tag_ids: string[] }) => {
|
||||
const { toggle_tag } = useTags();
|
||||
return (
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
<TagsWithBadge tag_ids={tag_ids} />
|
||||
<TagsWithBadge tag_ids={tag_ids} onBadgeClick={toggle_tag} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -343,7 +343,31 @@ export const atomWithStorage = <T>(key: string, init: T) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const tagsAtom = atomWithStorage<string[]>("tags-v0", []);
|
||||
const tagsAtom = atomWithStorage<string[]>("tags-v0", []);
|
||||
|
||||
export const useTags = () => {
|
||||
const [tags, setTags] = useAtom<string[]>(tagsAtom);
|
||||
|
||||
const add_tag = (tag_id: string) => setTags([...tags, tag_id]);
|
||||
const remove_tag = (tag_id: string) =>
|
||||
setTags(tags.filter((id) => id !== tag_id));
|
||||
const toggle_tag = (tag_id: string) => {
|
||||
if (tags.includes(tag_id)) {
|
||||
remove_tag(tag_id);
|
||||
} else {
|
||||
add_tag(tag_id);
|
||||
}
|
||||
};
|
||||
const clear_tags = () => setTags([]);
|
||||
|
||||
return {
|
||||
tags,
|
||||
add_tag,
|
||||
remove_tag,
|
||||
toggle_tag,
|
||||
clear_tags,
|
||||
};
|
||||
};
|
||||
|
||||
export const useTagsFilter = () => {
|
||||
const [tags] = useAtom<string[]>(tagsAtom);
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import * as monaco from "monaco-editor";
|
||||
|
||||
export const dockerfile_conf: monaco.languages.LanguageConfiguration = {
|
||||
brackets: [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: "{", close: "}" },
|
||||
{ open: "[", close: "]" },
|
||||
{ open: "(", close: ")" },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
surroundingPairs: [
|
||||
{ open: "{", close: "}" },
|
||||
{ open: "[", close: "]" },
|
||||
{ open: "(", close: ")" },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
};
|
||||
|
||||
export const dockerfile_language = <monaco.languages.IMonarchLanguage>{
|
||||
defaultToken: "",
|
||||
tokenPostfix: ".dockerfile",
|
||||
|
||||
variable: /\${?[\w]+}?/,
|
||||
|
||||
tokenizer: {
|
||||
root: [
|
||||
{ include: "@whitespace" },
|
||||
{ include: "@comment" },
|
||||
|
||||
[/(ONBUILD)(\s+)/, ["keyword", ""]],
|
||||
[
|
||||
/(ENV)(\s+)([\w]+)/,
|
||||
["keyword", "", { token: "variable", next: "@arguments" }],
|
||||
],
|
||||
[
|
||||
/(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|ARG|VOLUME|LABEL|USER|WORKDIR|COPY|CMD|STOPSIGNAL|SHELL|HEALTHCHECK|ENTRYPOINT)/,
|
||||
{ token: "keyword", next: "@arguments" },
|
||||
],
|
||||
],
|
||||
|
||||
arguments: [
|
||||
{ include: "@whitespace" },
|
||||
{ include: "@strings" },
|
||||
|
||||
[
|
||||
/(@variable)/,
|
||||
{
|
||||
cases: {
|
||||
"@eos": { token: "variable", next: "@popall" },
|
||||
"@default": "variable",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
/\\/,
|
||||
{
|
||||
cases: {
|
||||
"@eos": "",
|
||||
"@default": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
/./,
|
||||
{
|
||||
cases: {
|
||||
"@eos": { token: "", next: "@popall" },
|
||||
"@default": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
// Deal with white space, including comments
|
||||
whitespace: [
|
||||
[
|
||||
/\s+/,
|
||||
{
|
||||
cases: {
|
||||
"@eos": { token: "", next: "@popall" },
|
||||
"@default": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
comment: [[/(^#.*$)/, "comment", "@popall"]],
|
||||
|
||||
// Recognize strings, including those broken across lines with \ (but not without)
|
||||
strings: [
|
||||
[/\\'$/, "", "@popall"], // \' leaves @arguments at eol
|
||||
[/\\'/, ""], // \' is not a string
|
||||
[/'$/, "string", "@popall"],
|
||||
[/'/, "string", "@stringBody"],
|
||||
[/"$/, "string", "@popall"],
|
||||
[/"/, "string", "@dblStringBody"],
|
||||
],
|
||||
stringBody: [
|
||||
[
|
||||
/[^\\\$']/,
|
||||
{
|
||||
cases: {
|
||||
"@eos": { token: "string", next: "@popall" },
|
||||
"@default": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
[/\\./, "string.escape"],
|
||||
[/'$/, "string", "@popall"],
|
||||
[/'/, "string", "@pop"],
|
||||
[/(@variable)/, "variable"],
|
||||
|
||||
[/\\$/, "string"],
|
||||
[/$/, "string", "@popall"],
|
||||
],
|
||||
dblStringBody: [
|
||||
[
|
||||
/[^\\\$"]/,
|
||||
{
|
||||
cases: {
|
||||
"@eos": { token: "string", next: "@popall" },
|
||||
"@default": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
[/\\./, "string.escape"],
|
||||
[/"$/, "string", "@popall"],
|
||||
[/"/, "string", "@pop"],
|
||||
[/(@variable)/, "variable"],
|
||||
|
||||
[/\\$/, "string"],
|
||||
[/$/, "string", "@popall"],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
monaco.languages.register({ id: "dockerfile" });
|
||||
monaco.languages.setLanguageConfiguration("dockerfile", dockerfile_conf);
|
||||
monaco.languages.setMonarchTokensProvider("dockerfile", dockerfile_language);
|
||||
@@ -31,8 +31,6 @@ import "./theme";
|
||||
// Load the parsers
|
||||
import "./yaml";
|
||||
import "./toml";
|
||||
import "./shell"
|
||||
import "./key_value";
|
||||
import "./string_list";
|
||||
import "./shell";
|
||||
import "./dockerfile";
|
||||
import "./rust";
|
||||
|
||||
@@ -1,370 +0,0 @@
|
||||
import * as monaco from "monaco-editor";
|
||||
|
||||
export const rust_conf: monaco.languages.LanguageConfiguration = {
|
||||
comments: {
|
||||
lineComment: "//",
|
||||
blockComment: ["/*", "*/"],
|
||||
},
|
||||
brackets: [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: "[", close: "]" },
|
||||
{ open: "{", close: "}" },
|
||||
{ open: "(", close: ")" },
|
||||
{ open: '"', close: '"', notIn: ["string"] },
|
||||
],
|
||||
surroundingPairs: [
|
||||
{ open: "{", close: "}" },
|
||||
{ open: "[", close: "]" },
|
||||
{ open: "(", close: ")" },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
folding: {
|
||||
markers: {
|
||||
start: new RegExp("^\\s*#pragma\\s+region\\b"),
|
||||
end: new RegExp("^\\s*#pragma\\s+endregion\\b"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const rust_language = <monaco.languages.IMonarchLanguage>{
|
||||
tokenPostfix: ".rust",
|
||||
defaultToken: "invalid",
|
||||
keywords: [
|
||||
"as",
|
||||
"async",
|
||||
"await",
|
||||
"box",
|
||||
"break",
|
||||
"const",
|
||||
"continue",
|
||||
"crate",
|
||||
"dyn",
|
||||
"else",
|
||||
"enum",
|
||||
"extern",
|
||||
"false",
|
||||
"fn",
|
||||
"for",
|
||||
"if",
|
||||
"impl",
|
||||
"in",
|
||||
"let",
|
||||
"loop",
|
||||
"match",
|
||||
"mod",
|
||||
"move",
|
||||
"mut",
|
||||
"pub",
|
||||
"ref",
|
||||
"return",
|
||||
"self",
|
||||
"static",
|
||||
"struct",
|
||||
"super",
|
||||
"trait",
|
||||
"true",
|
||||
"try",
|
||||
"type",
|
||||
"unsafe",
|
||||
"use",
|
||||
"where",
|
||||
"while",
|
||||
"catch",
|
||||
"default",
|
||||
"union",
|
||||
"static",
|
||||
"abstract",
|
||||
"alignof",
|
||||
"become",
|
||||
"do",
|
||||
"final",
|
||||
"macro",
|
||||
"offsetof",
|
||||
"override",
|
||||
"priv",
|
||||
"proc",
|
||||
"pure",
|
||||
"sizeof",
|
||||
"typeof",
|
||||
"unsized",
|
||||
"virtual",
|
||||
"yield",
|
||||
],
|
||||
|
||||
typeKeywords: [
|
||||
"Self",
|
||||
"m32",
|
||||
"m64",
|
||||
"m128",
|
||||
"f80",
|
||||
"f16",
|
||||
"f128",
|
||||
"int",
|
||||
"uint",
|
||||
"float",
|
||||
"char",
|
||||
"bool",
|
||||
"u8",
|
||||
"u16",
|
||||
"u32",
|
||||
"u64",
|
||||
"f32",
|
||||
"f64",
|
||||
"i8",
|
||||
"i16",
|
||||
"i32",
|
||||
"i64",
|
||||
"str",
|
||||
"Option",
|
||||
"Either",
|
||||
"c_float",
|
||||
"c_double",
|
||||
"c_void",
|
||||
"FILE",
|
||||
"fpos_t",
|
||||
"DIR",
|
||||
"dirent",
|
||||
"c_char",
|
||||
"c_schar",
|
||||
"c_uchar",
|
||||
"c_short",
|
||||
"c_ushort",
|
||||
"c_int",
|
||||
"c_uint",
|
||||
"c_long",
|
||||
"c_ulong",
|
||||
"size_t",
|
||||
"ptrdiff_t",
|
||||
"clock_t",
|
||||
"time_t",
|
||||
"c_longlong",
|
||||
"c_ulonglong",
|
||||
"intptr_t",
|
||||
"uintptr_t",
|
||||
"off_t",
|
||||
"dev_t",
|
||||
"ino_t",
|
||||
"pid_t",
|
||||
"mode_t",
|
||||
"ssize_t",
|
||||
],
|
||||
|
||||
constants: ["true", "false", "Some", "None", "Left", "Right", "Ok", "Err"],
|
||||
|
||||
supportConstants: [
|
||||
"EXIT_FAILURE",
|
||||
"EXIT_SUCCESS",
|
||||
"RAND_MAX",
|
||||
"EOF",
|
||||
"SEEK_SET",
|
||||
"SEEK_CUR",
|
||||
"SEEK_END",
|
||||
"_IOFBF",
|
||||
"_IONBF",
|
||||
"_IOLBF",
|
||||
"BUFSIZ",
|
||||
"FOPEN_MAX",
|
||||
"FILENAME_MAX",
|
||||
"L_tmpnam",
|
||||
"TMP_MAX",
|
||||
"O_RDONLY",
|
||||
"O_WRONLY",
|
||||
"O_RDWR",
|
||||
"O_APPEND",
|
||||
"O_CREAT",
|
||||
"O_EXCL",
|
||||
"O_TRUNC",
|
||||
"S_IFIFO",
|
||||
"S_IFCHR",
|
||||
"S_IFBLK",
|
||||
"S_IFDIR",
|
||||
"S_IFREG",
|
||||
"S_IFMT",
|
||||
"S_IEXEC",
|
||||
"S_IWRITE",
|
||||
"S_IREAD",
|
||||
"S_IRWXU",
|
||||
"S_IXUSR",
|
||||
"S_IWUSR",
|
||||
"S_IRUSR",
|
||||
"F_OK",
|
||||
"R_OK",
|
||||
"W_OK",
|
||||
"X_OK",
|
||||
"STDIN_FILENO",
|
||||
"STDOUT_FILENO",
|
||||
"STDERR_FILENO",
|
||||
],
|
||||
|
||||
supportMacros: [
|
||||
"format!",
|
||||
"print!",
|
||||
"println!",
|
||||
"panic!",
|
||||
"format_args!",
|
||||
"unreachable!",
|
||||
"write!",
|
||||
"writeln!",
|
||||
],
|
||||
|
||||
operators: [
|
||||
"!",
|
||||
"!=",
|
||||
"%",
|
||||
"%=",
|
||||
"&",
|
||||
"&=",
|
||||
"&&",
|
||||
"*",
|
||||
"*=",
|
||||
"+",
|
||||
"+=",
|
||||
"-",
|
||||
"-=",
|
||||
"->",
|
||||
".",
|
||||
"..",
|
||||
"...",
|
||||
"/",
|
||||
"/=",
|
||||
":",
|
||||
";",
|
||||
"<<",
|
||||
"<<=",
|
||||
"<",
|
||||
"<=",
|
||||
"=",
|
||||
"==",
|
||||
"=>",
|
||||
">",
|
||||
">=",
|
||||
">>",
|
||||
">>=",
|
||||
"@",
|
||||
"^",
|
||||
"^=",
|
||||
"|",
|
||||
"|=",
|
||||
"||",
|
||||
"_",
|
||||
"?",
|
||||
"#",
|
||||
],
|
||||
|
||||
escapes: /\\([nrt0\"''\\]|x\h{2}|u\{\h{1,6}\})/,
|
||||
delimiters: /[,]/,
|
||||
symbols: /[\#\!\%\&\*\+\-\.\/\:\;\<\=\>\@\^\|_\?]+/,
|
||||
intSuffixes: /[iu](8|16|32|64|128|size)/,
|
||||
floatSuffixes: /f(32|64)/,
|
||||
|
||||
tokenizer: {
|
||||
root: [
|
||||
// Raw string literals
|
||||
[
|
||||
/r(#*)"/,
|
||||
{ token: "string.quote", bracket: "@open", next: "@stringraw.$1" },
|
||||
],
|
||||
[
|
||||
/[a-zA-Z][a-zA-Z0-9_]*!?|_[a-zA-Z0-9_]+/,
|
||||
{
|
||||
cases: {
|
||||
"@typeKeywords": "keyword.type",
|
||||
"@keywords": "keyword",
|
||||
"@supportConstants": "keyword",
|
||||
"@supportMacros": "keyword",
|
||||
"@constants": "keyword",
|
||||
"@default": "identifier",
|
||||
},
|
||||
},
|
||||
],
|
||||
// Designator
|
||||
[/\$/, "identifier"],
|
||||
// Lifetime annotations
|
||||
[/'[a-zA-Z_][a-zA-Z0-9_]*(?=[^\'])/, "identifier"],
|
||||
// Byte literal
|
||||
[/'(\S|@escapes)'/, "string.byteliteral"],
|
||||
// Strings
|
||||
[/"/, { token: "string.quote", bracket: "@open", next: "@string" }],
|
||||
{ include: "@numbers" },
|
||||
// Whitespace + comments
|
||||
{ include: "@whitespace" },
|
||||
[
|
||||
/@delimiters/,
|
||||
{
|
||||
cases: {
|
||||
"@keywords": "keyword",
|
||||
"@default": "delimiter",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
[/[{}()\[\]<>]/, "@brackets"],
|
||||
[/@symbols/, { cases: { "@operators": "operator", "@default": "" } }],
|
||||
],
|
||||
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, "white"],
|
||||
[/\/\*/, "comment", "@comment"],
|
||||
[/\/\/.*$/, "comment"],
|
||||
],
|
||||
|
||||
comment: [
|
||||
[/[^\/*]+/, "comment"],
|
||||
[/\/\*/, "comment", "@push"],
|
||||
["\\*/", "comment", "@pop"],
|
||||
[/[\/*]/, "comment"],
|
||||
],
|
||||
|
||||
string: [
|
||||
[/[^\\"]+/, "string"],
|
||||
[/@escapes/, "string.escape"],
|
||||
[/\\./, "string.escape.invalid"],
|
||||
[/"/, { token: "string.quote", bracket: "@close", next: "@pop" }],
|
||||
],
|
||||
|
||||
stringraw: [
|
||||
[/[^"#]+/, { token: "string" }],
|
||||
[
|
||||
/"(#*)/,
|
||||
{
|
||||
cases: {
|
||||
"$1==$S2": {
|
||||
token: "string.quote",
|
||||
bracket: "@close",
|
||||
next: "@pop",
|
||||
},
|
||||
"@default": { token: "string" },
|
||||
},
|
||||
},
|
||||
],
|
||||
[/["#]/, { token: "string" }],
|
||||
],
|
||||
|
||||
numbers: [
|
||||
//Octal
|
||||
[/(0o[0-7_]+)(@intSuffixes)?/, { token: "number" }],
|
||||
//Binary
|
||||
[/(0b[0-1_]+)(@intSuffixes)?/, { token: "number" }],
|
||||
//Exponent
|
||||
[
|
||||
/[\d][\d_]*(\.[\d][\d_]*)?[eE][+-][\d_]+(@floatSuffixes)?/,
|
||||
{ token: "number" },
|
||||
],
|
||||
//Float
|
||||
[/\b(\d\.?[\d_]*)(@floatSuffixes)?\b/, { token: "number" }],
|
||||
//Hexadecimal
|
||||
[/(0x[\da-fA-F]+)_?(@intSuffixes)?/, { token: "number" }],
|
||||
//Integer
|
||||
[/[\d][\d_]*(@intSuffixes?)?/, { token: "number" }],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
monaco.languages.register({ id: "rust" });
|
||||
monaco.languages.setLanguageConfiguration("rust", rust_conf);
|
||||
monaco.languages.setMonarchTokensProvider("rust", rust_language);
|
||||
+3
-3
@@ -64,13 +64,13 @@ docker compose -p komodo-dev -f dev.compose.yaml build"""
|
||||
[dev-docsite]
|
||||
description = "starts the documentation site (https://komo.do) in dev mode"
|
||||
path = "docsite"
|
||||
cmd = "yarn start"
|
||||
cmd = "yarn && yarn start"
|
||||
|
||||
[deploy-docsite]
|
||||
description = "deploys the documentation site (https://komo.do) to github pages"
|
||||
path = "docsite"
|
||||
cmd = "yarn deploy"
|
||||
cmd = "yarn && yarn deploy"
|
||||
|
||||
[dev-rustdoc]
|
||||
description = "starts the rustdoc site (https://docs.rs/komodo_client/latest/komodo_client/) in dev mode"
|
||||
cmd = "cargo watch -s 'cargo doc --no-deps -p komodo_client' & http --quiet target/doc"
|
||||
cmd = "cargo doc --no-deps -p komodo_client && http-server -p 8050 target/doc"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user