forked from github-starred/komodo
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cfae525e9 | ||
|
|
80e5d2a972 | ||
|
|
6f22c011a6 | ||
|
|
401cccee79 | ||
|
|
654b923f98 | ||
|
|
61261be70f | ||
|
|
46418125e3 |
@@ -5,4 +5,10 @@ LICENSE
|
||||
*.code-workspace
|
||||
|
||||
*/node_modules
|
||||
*/dist
|
||||
*/dist
|
||||
|
||||
creds.toml
|
||||
.core-repos
|
||||
.repos
|
||||
.stacks
|
||||
.ssl
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,11 +1,14 @@
|
||||
target
|
||||
/frontend/build
|
||||
node_modules
|
||||
/lib/ts_client/build
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
.env.development
|
||||
.DS_Store
|
||||
|
||||
creds.toml
|
||||
.syncs
|
||||
.core-repos
|
||||
.repos
|
||||
.stacks
|
||||
.DS_Store
|
||||
.ssl
|
||||
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -41,7 +41,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -943,7 +943,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"komodo_client",
|
||||
"run_command",
|
||||
@@ -1355,7 +1355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment_file"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@@ -1439,7 +1439,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -1571,7 +1571,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
@@ -2192,7 +2192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_cli"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2208,7 +2208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_client"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2239,7 +2239,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_core"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2296,7 +2296,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_periphery"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2306,6 +2306,7 @@ dependencies = [
|
||||
"bollard",
|
||||
"clap",
|
||||
"command",
|
||||
"derive_variants",
|
||||
"dotenvy",
|
||||
"environment_file",
|
||||
"envy",
|
||||
@@ -2382,7 +2383,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -2446,7 +2447,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenvy",
|
||||
@@ -3101,7 +3102,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -4879,7 +4880,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
|
||||
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.15.2"
|
||||
version = "1.15.3"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
@@ -10,36 +10,42 @@ use komodo_client::entities::{
|
||||
ResourceTargetVariant,
|
||||
};
|
||||
use mungos::{find::find_collect, mongodb::bson::doc};
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::{config::core_config, state::db_client};
|
||||
|
||||
mod discord;
|
||||
mod slack;
|
||||
|
||||
#[instrument]
|
||||
pub async fn send_alerts(alerts: &[Alert]) {
|
||||
if alerts.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(alerters) = find_collect(
|
||||
&db_client().alerters,
|
||||
doc! { "config.enabled": true },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!(
|
||||
let span =
|
||||
info_span!("send_alerts", alerts = format!("{alerts:?}"));
|
||||
async {
|
||||
let Ok(alerters) = find_collect(
|
||||
&db_client().alerters,
|
||||
doc! { "config.enabled": true },
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!(
|
||||
"ERROR sending alerts | failed to get alerters from db | {e:#}"
|
||||
)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let handles =
|
||||
alerts.iter().map(|alert| send_alert(&alerters, alert));
|
||||
let handles =
|
||||
alerts.iter().map(|alert| send_alert(&alerters, alert));
|
||||
|
||||
join_all(handles).await;
|
||||
join_all(handles).await;
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::time::Instant;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{middleware, routing::post, Extension, Router};
|
||||
use derive_variants::{EnumVariants, ExtractVariant};
|
||||
use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
api::execute::*,
|
||||
@@ -33,7 +34,10 @@ mod stack;
|
||||
mod sync;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants,
|
||||
)]
|
||||
#[variant_derive(Debug)]
|
||||
#[resolver_target(State)]
|
||||
#[resolver_args((User, Update))]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
@@ -154,7 +158,15 @@ async fn handler(
|
||||
Ok(Json(update))
|
||||
}
|
||||
|
||||
#[instrument(name = "ExecuteRequest", skip(user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
#[instrument(
|
||||
name = "ExecuteRequest",
|
||||
skip(user, update),
|
||||
fields(
|
||||
user_id = user.id,
|
||||
update_id = update.id,
|
||||
request = format!("{:?}", request.extract_variant()))
|
||||
)
|
||||
]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: ExecuteRequest,
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::{
|
||||
add_interp_update_log,
|
||||
interpolate_variables_secrets_into_extra_args,
|
||||
interpolate_variables_secrets_into_string,
|
||||
interpolate_variables_secrets_into_system_command,
|
||||
},
|
||||
periphery_client,
|
||||
query::get_variables_and_secrets,
|
||||
@@ -102,6 +103,13 @@ impl Resolve<DeployStack, (User, Update)> for State {
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
interpolate_variables_secrets_into_system_command(
|
||||
&vars_and_secrets,
|
||||
&mut stack.config.pre_deploy,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
add_interp_update_log(
|
||||
&mut update,
|
||||
&global_replacers,
|
||||
|
||||
@@ -188,7 +188,10 @@ async fn handler(
|
||||
#[instrument(
|
||||
name = "WriteRequest",
|
||||
skip(user, request),
|
||||
fields(user_id = user.id, request = format!("{:?}", request.extract_variant()))
|
||||
fields(
|
||||
user_id = user.id,
|
||||
request = format!("{:?}", request.extract_variant())
|
||||
)
|
||||
)]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
|
||||
@@ -57,6 +57,7 @@ impl super::KomodoResource for Build {
|
||||
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,
|
||||
built_hash: build.info.built_hash,
|
||||
|
||||
@@ -15,7 +15,7 @@ use komodo_client::{
|
||||
tag::Tag,
|
||||
to_komodo_name,
|
||||
update::Update,
|
||||
user::User,
|
||||
user::{system_user, User},
|
||||
Operation, ResourceTarget, ResourceTargetVariant,
|
||||
},
|
||||
};
|
||||
@@ -508,6 +508,18 @@ pub async fn create<T: KomodoResource>(
|
||||
return Err(anyhow!("valid ObjectIds cannot be used as names."));
|
||||
}
|
||||
|
||||
// Ensure an existing resource with same name doesn't already exist
|
||||
// The database indexing also ensures this but doesn't give a good error message.
|
||||
if list_full_for_user::<T>(Default::default(), system_user())
|
||||
.await
|
||||
.context("Failed to list all resources for duplicate name check")?
|
||||
.into_iter()
|
||||
.find(|r| r.name == name)
|
||||
.is_some()
|
||||
{
|
||||
return Err(anyhow!("Must provide unique name for resource."));
|
||||
}
|
||||
|
||||
let start_ts = komodo_timestamp();
|
||||
|
||||
T::validate_create_config(&mut config, user).await?;
|
||||
|
||||
@@ -353,7 +353,7 @@ impl ToToml for ServerTemplate {
|
||||
if empty_params {
|
||||
// toml_pretty will remove empty map
|
||||
// but in this case its needed to deserialize the enums.
|
||||
toml.push_str("\nconfig.params = {}");
|
||||
toml.push_str("\nparams = {}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,7 +385,7 @@ impl ToToml for Builder {
|
||||
if empty_params {
|
||||
// toml_pretty will remove empty map
|
||||
// but in this case its needed to deserialize the enums.
|
||||
toml.push_str("\nconfig.params = {}");
|
||||
toml.push_str("\nparams = {}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ git.workspace = true
|
||||
serror = { workspace = true, features = ["axum"] }
|
||||
merge_config_files.workspace = true
|
||||
async_timing_util.workspace = true
|
||||
derive_variants.workspace = true
|
||||
resolver_api.workspace = true
|
||||
run_command.workspace = true
|
||||
svi.workspace = true
|
||||
|
||||
@@ -76,85 +76,6 @@ pub struct DockerComposeLsItem {
|
||||
|
||||
//
|
||||
|
||||
const DEFAULT_COMPOSE_CONTENTS: &str = "## 🦎 Hello Komodo 🦎
|
||||
services:
|
||||
hello_world:
|
||||
image: hello-world
|
||||
# networks:
|
||||
# - default
|
||||
# ports:
|
||||
# - 3000:3000
|
||||
# volumes:
|
||||
# - data:/data
|
||||
|
||||
# networks:
|
||||
# default: {}
|
||||
|
||||
# volumes:
|
||||
# data:
|
||||
";
|
||||
|
||||
impl Resolve<GetComposeContentsOnHost, ()> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetComposeContentsOnHost {
|
||||
name,
|
||||
run_directory,
|
||||
file_paths,
|
||||
}: GetComposeContentsOnHost,
|
||||
_: (),
|
||||
) -> anyhow::Result<GetComposeContentsOnHostResponse> {
|
||||
let root =
|
||||
periphery_config().stack_dir.join(to_komodo_name(&name));
|
||||
let run_directory =
|
||||
root.join(&run_directory).components().collect::<PathBuf>();
|
||||
|
||||
if !run_directory.exists() {
|
||||
fs::create_dir_all(&run_directory)
|
||||
.await
|
||||
.context("Failed to initialize run directory")?;
|
||||
}
|
||||
|
||||
let file_paths = file_paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
run_directory.join(path).components().collect::<PathBuf>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut res = GetComposeContentsOnHostResponse::default();
|
||||
|
||||
for full_path in &file_paths {
|
||||
if !full_path.exists() {
|
||||
fs::write(&full_path, DEFAULT_COMPOSE_CONTENTS)
|
||||
.await
|
||||
.context("Failed to init missing compose file on host")?;
|
||||
}
|
||||
match fs::read_to_string(&full_path).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to read compose file contents at {full_path:?}"
|
||||
)
|
||||
}) {
|
||||
Ok(contents) => {
|
||||
res.contents.push(FileContents {
|
||||
path: full_path.display().to_string(),
|
||||
contents,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
res.errors.push(FileContents {
|
||||
path: full_path.display().to_string(),
|
||||
contents: format_serror(&e.into()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<GetComposeServiceLog> for State {
|
||||
#[instrument(
|
||||
name = "GetComposeServiceLog",
|
||||
@@ -204,6 +125,85 @@ impl Resolve<GetComposeServiceLogSearch> for State {
|
||||
|
||||
//
|
||||
|
||||
const DEFAULT_COMPOSE_CONTENTS: &str = "## 🦎 Hello Komodo 🦎
|
||||
services:
|
||||
hello_world:
|
||||
image: hello-world
|
||||
# networks:
|
||||
# - default
|
||||
# ports:
|
||||
# - 3000:3000
|
||||
# volumes:
|
||||
# - data:/data
|
||||
|
||||
# networks:
|
||||
# default: {}
|
||||
|
||||
# volumes:
|
||||
# data:
|
||||
";
|
||||
|
||||
impl Resolve<GetComposeContentsOnHost, ()> for State {
|
||||
#[instrument(
|
||||
name = "GetComposeContentsOnHost",
|
||||
level = "debug",
|
||||
skip(self)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetComposeContentsOnHost {
|
||||
name,
|
||||
run_directory,
|
||||
file_paths,
|
||||
}: GetComposeContentsOnHost,
|
||||
_: (),
|
||||
) -> anyhow::Result<GetComposeContentsOnHostResponse> {
|
||||
let root =
|
||||
periphery_config().stack_dir.join(to_komodo_name(&name));
|
||||
let run_directory =
|
||||
root.join(&run_directory).components().collect::<PathBuf>();
|
||||
|
||||
if !run_directory.exists() {
|
||||
fs::create_dir_all(&run_directory)
|
||||
.await
|
||||
.context("Failed to initialize run directory")?;
|
||||
}
|
||||
|
||||
let mut res = GetComposeContentsOnHostResponse::default();
|
||||
|
||||
for path in file_paths {
|
||||
let full_path =
|
||||
run_directory.join(&path).components().collect::<PathBuf>();
|
||||
if !full_path.exists() {
|
||||
fs::write(&full_path, DEFAULT_COMPOSE_CONTENTS)
|
||||
.await
|
||||
.context("Failed to init missing compose file on host")?;
|
||||
}
|
||||
match fs::read_to_string(&full_path).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to read compose file contents at {full_path:?}"
|
||||
)
|
||||
}) {
|
||||
Ok(contents) => {
|
||||
// The path we store here has to be the same as incoming file path in the array,
|
||||
// in order for WriteComposeContentsToHost to write to the correct path.
|
||||
res.contents.push(FileContents { path, contents });
|
||||
}
|
||||
Err(e) => {
|
||||
res.errors.push(FileContents {
|
||||
path,
|
||||
contents: format_serror(&e.into()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Resolve<WriteComposeContentsToHost> for State {
|
||||
#[instrument(name = "WriteComposeContentsToHost", skip(self))]
|
||||
async fn resolve(
|
||||
@@ -216,13 +216,10 @@ impl Resolve<WriteComposeContentsToHost> for State {
|
||||
}: WriteComposeContentsToHost,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let root =
|
||||
periphery_config().stack_dir.join(to_komodo_name(&name));
|
||||
let run_directory = root.join(&run_directory);
|
||||
let run_directory = run_directory.canonicalize().context(
|
||||
"failed to validate run directory on host (canonicalize error)",
|
||||
)?;
|
||||
let file_path = run_directory
|
||||
let file_path = periphery_config()
|
||||
.stack_dir
|
||||
.join(to_komodo_name(&name))
|
||||
.join(&run_directory)
|
||||
.join(file_path)
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_command;
|
||||
use derive_variants::EnumVariants;
|
||||
use futures::TryFutureExt;
|
||||
use komodo_client::entities::{update::Log, SystemCommand};
|
||||
use periphery_client::api::{
|
||||
@@ -30,7 +31,10 @@ mod network;
|
||||
mod stats;
|
||||
mod volume;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants,
|
||||
)]
|
||||
#[variant_derive(Debug)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
#[resolver_target(State)]
|
||||
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
|
||||
@@ -206,7 +210,7 @@ impl ResolveToString<ListSecrets> for State {
|
||||
}
|
||||
|
||||
impl Resolve<GetDockerLists> for State {
|
||||
#[instrument(name = "GetDockerLists", skip(self))]
|
||||
#[instrument(name = "GetDockerLists", level = "debug", skip(self))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDockerLists {}: GetDockerLists,
|
||||
|
||||
@@ -10,7 +10,7 @@ use komodo_client::entities::{
|
||||
};
|
||||
use periphery_client::api::{
|
||||
compose::ComposeUpResponse,
|
||||
git::{PullOrCloneRepo, RepoActionResponse},
|
||||
git::{CloneRepo, PullOrCloneRepo, RepoActionResponse},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
use tokio::fs;
|
||||
@@ -71,7 +71,7 @@ pub async fn compose_up(
|
||||
return Err(anyhow!("A compose file doesn't exist after writing stack. Ensure the run_directory and file_paths are correct."));
|
||||
}
|
||||
|
||||
for (_, full_path) in &file_paths {
|
||||
for (path, full_path) in &file_paths {
|
||||
let file_contents =
|
||||
match fs::read_to_string(&full_path).await.with_context(|| {
|
||||
format!(
|
||||
@@ -86,7 +86,7 @@ pub async fn compose_up(
|
||||
.push(Log::error("read compose file", error.clone()));
|
||||
// This should only happen for repo stacks, ie remote error
|
||||
res.remote_errors.push(FileContents {
|
||||
path: full_path.display().to_string(),
|
||||
path: path.to_string(),
|
||||
contents: error,
|
||||
});
|
||||
return Err(anyhow!(
|
||||
@@ -95,7 +95,7 @@ pub async fn compose_up(
|
||||
}
|
||||
};
|
||||
res.file_contents.push(FileContents {
|
||||
path: full_path.display().to_string(),
|
||||
path: path.to_string(),
|
||||
contents: file_contents,
|
||||
});
|
||||
}
|
||||
@@ -137,7 +137,7 @@ pub async fn compose_up(
|
||||
}
|
||||
|
||||
let env_file = env_file_path
|
||||
.map(|path| format!(" --env-file {}", path.display()))
|
||||
.map(|path| format!(" --env-file {path}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Build images before destroying to minimize downtime.
|
||||
@@ -197,6 +197,64 @@ pub async fn compose_up(
|
||||
}
|
||||
}
|
||||
|
||||
if !stack.config.pre_deploy.command.is_empty() {
|
||||
let pre_deploy_path =
|
||||
run_directory.join(&stack.config.pre_deploy.path);
|
||||
if !stack.config.skip_secret_interp {
|
||||
let (full_command, mut replacers) = svi::interpolate_variables(
|
||||
&stack.config.pre_deploy.command,
|
||||
&periphery_config().secrets,
|
||||
svi::Interpolator::DoubleBrackets,
|
||||
true,
|
||||
)
|
||||
.context(
|
||||
"failed to interpolate secrets into pre_deploy command",
|
||||
)?;
|
||||
replacers.extend(core_replacers.to_owned());
|
||||
let mut pre_deploy_log = run_komodo_command(
|
||||
"pre deploy",
|
||||
format!("cd {} && {full_command}", pre_deploy_path.display()),
|
||||
)
|
||||
.await;
|
||||
|
||||
pre_deploy_log.command =
|
||||
svi::replace_in_string(&pre_deploy_log.command, &replacers);
|
||||
pre_deploy_log.stdout =
|
||||
svi::replace_in_string(&pre_deploy_log.stdout, &replacers);
|
||||
pre_deploy_log.stderr =
|
||||
svi::replace_in_string(&pre_deploy_log.stderr, &replacers);
|
||||
|
||||
tracing::debug!(
|
||||
"run Stack pre_deploy command | command: {} | cwd: {:?}",
|
||||
pre_deploy_log.command,
|
||||
pre_deploy_path
|
||||
);
|
||||
|
||||
res.logs.push(pre_deploy_log);
|
||||
} else {
|
||||
let pre_deploy_log = run_komodo_command(
|
||||
"pre deploy",
|
||||
format!(
|
||||
"cd {} && {}",
|
||||
pre_deploy_path.display(),
|
||||
stack.config.pre_deploy.command
|
||||
),
|
||||
)
|
||||
.await;
|
||||
tracing::debug!(
|
||||
"run Stack pre_deploy command | command: {} | cwd: {:?}",
|
||||
&stack.config.pre_deploy.command,
|
||||
pre_deploy_path
|
||||
);
|
||||
res.logs.push(pre_deploy_log);
|
||||
}
|
||||
if !all_logs_success(&res.logs) {
|
||||
return Err(anyhow!(
|
||||
"Failed at running pre_deploy command, stopping the run."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Take down the existing containers.
|
||||
// This one tries to use the previously deployed service name, to ensure the right stack is taken down.
|
||||
compose_down(&last_project_name, service, res)
|
||||
@@ -237,11 +295,11 @@ pub async fn compose_up(
|
||||
|
||||
/// Either writes the stack file_contents to a file, or clones the repo.
|
||||
/// Returns (run_directory, env_file_path)
|
||||
async fn write_stack(
|
||||
stack: &Stack,
|
||||
async fn write_stack<'a>(
|
||||
stack: &'a Stack,
|
||||
git_token: Option<String>,
|
||||
res: &mut ComposeUpResponse,
|
||||
) -> anyhow::Result<(PathBuf, Option<PathBuf>)> {
|
||||
) -> anyhow::Result<(PathBuf, Option<&'a str>)> {
|
||||
let root = periphery_config()
|
||||
.stack_dir
|
||||
.join(to_komodo_name(&stack.name));
|
||||
@@ -275,7 +333,14 @@ async fn write_stack(
|
||||
return Err(anyhow!("failed to write environment file"));
|
||||
}
|
||||
};
|
||||
Ok((run_directory, env_file_path))
|
||||
Ok((
|
||||
run_directory,
|
||||
// Env file paths are already relative to run directory,
|
||||
// so need to pass original env_file_path here.
|
||||
env_file_path
|
||||
.is_some()
|
||||
.then_some(&stack.config.env_file_path),
|
||||
))
|
||||
} else if stack.config.repo.is_empty() {
|
||||
if stack.config.file_contents.trim().is_empty() {
|
||||
return Err(anyhow!("Must either input compose file contents directly, or use file one host / git repo options."));
|
||||
@@ -324,7 +389,12 @@ async fn write_stack(
|
||||
format!("failed to write compose file to {file_path:?}")
|
||||
})?;
|
||||
|
||||
Ok((run_directory, env_file_path))
|
||||
Ok((
|
||||
run_directory,
|
||||
env_file_path
|
||||
.is_some()
|
||||
.then_some(&stack.config.env_file_path),
|
||||
))
|
||||
} else {
|
||||
// ================
|
||||
// REPO BASED FILES
|
||||
@@ -362,27 +432,46 @@ async fn write_stack(
|
||||
}
|
||||
};
|
||||
|
||||
let clone_or_pull_res = if stack.config.reclone {
|
||||
State
|
||||
.resolve(
|
||||
CloneRepo {
|
||||
args,
|
||||
git_token,
|
||||
environment: env_vars,
|
||||
env_file_path: stack.config.env_file_path.clone(),
|
||||
skip_secret_interp: stack.config.skip_secret_interp,
|
||||
// repo replacer only needed for on_clone / on_pull,
|
||||
// which aren't available for stacks
|
||||
replacers: Default::default(),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
State
|
||||
.resolve(
|
||||
PullOrCloneRepo {
|
||||
args,
|
||||
git_token,
|
||||
environment: env_vars,
|
||||
env_file_path: stack.config.env_file_path.clone(),
|
||||
skip_secret_interp: stack.config.skip_secret_interp,
|
||||
// repo replacer only needed for on_clone / on_pull,
|
||||
// which aren't available for stacks
|
||||
replacers: Default::default(),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
let RepoActionResponse {
|
||||
logs,
|
||||
commit_hash,
|
||||
commit_message,
|
||||
env_file_path,
|
||||
} = match State
|
||||
.resolve(
|
||||
PullOrCloneRepo {
|
||||
args,
|
||||
git_token,
|
||||
environment: env_vars,
|
||||
env_file_path: stack.config.env_file_path.clone(),
|
||||
skip_secret_interp: stack.config.skip_secret_interp,
|
||||
// repo replacer only needed for on_clone / on_pull,
|
||||
// which aren't available for stacks
|
||||
replacers: Default::default(),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
} = match clone_or_pull_res {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
let error = format_serror(
|
||||
@@ -407,7 +496,12 @@ async fn write_stack(
|
||||
return Err(anyhow!("Stopped after repo pull failure"));
|
||||
}
|
||||
|
||||
Ok((run_directory, env_file_path))
|
||||
Ok((
|
||||
run_directory,
|
||||
env_file_path
|
||||
.is_some()
|
||||
.then_some(&stack.config.env_file_path),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +516,7 @@ async fn compose_down(
|
||||
.map(|service| format!(" {service}"))
|
||||
.unwrap_or_default();
|
||||
let log = run_komodo_command(
|
||||
"destroy container",
|
||||
"compose down",
|
||||
format!("{docker_compose} -p {project} down{service_arg}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{net::SocketAddr, time::Instant};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{
|
||||
@@ -11,6 +11,7 @@ use axum::{
|
||||
Router,
|
||||
};
|
||||
use axum_extra::{headers::ContentType, TypedHeader};
|
||||
use derive_variants::ExtractVariant;
|
||||
use resolver_api::Resolver;
|
||||
use serror::{AddStatusCode, AddStatusCodeError, Json};
|
||||
use uuid::Uuid;
|
||||
@@ -40,13 +41,12 @@ async fn handler(
|
||||
Ok((TypedHeader(ContentType::json()), res??))
|
||||
}
|
||||
|
||||
#[instrument(name = "PeripheryHandler")]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: crate::api::PeripheryRequest,
|
||||
) -> anyhow::Result<String> {
|
||||
let timer = Instant::now();
|
||||
|
||||
let variant = request.extract_variant();
|
||||
|
||||
let res =
|
||||
State
|
||||
.resolve_request(request, ())
|
||||
@@ -59,16 +59,12 @@ async fn task(
|
||||
});
|
||||
|
||||
if let Err(e) = &res {
|
||||
warn!("request {req_id} error: {e:#}");
|
||||
warn!("request {req_id} | type: {variant:?} | error: {e:#}");
|
||||
}
|
||||
|
||||
let elapsed = timer.elapsed();
|
||||
debug!("request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
async fn guard_request_by_passkey(
|
||||
req: Request<Body>,
|
||||
next: Next,
|
||||
@@ -100,7 +96,6 @@ async fn guard_request_by_passkey(
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
async fn guard_request_by_ip(
|
||||
req: Request<Body>,
|
||||
next: Next,
|
||||
|
||||
@@ -33,6 +33,8 @@ pub struct BuildListItemInfo {
|
||||
pub builder_id: String,
|
||||
/// The git provider domain
|
||||
pub git_provider: String,
|
||||
/// The image registry domain
|
||||
pub image_registry_domain: String,
|
||||
/// The repo used as the source of the build
|
||||
pub repo: String,
|
||||
/// The branch of the repo
|
||||
|
||||
@@ -87,7 +87,7 @@ pub struct Env {
|
||||
/// If not provided, will use Default config.
|
||||
///
|
||||
/// Note. This is overridden if the equivalent arg is passed in [CliArgs].
|
||||
#[serde(default)]
|
||||
#[serde(default, alias = "periphery_config_path")]
|
||||
pub periphery_config_paths: Vec<String>,
|
||||
/// If specifying folders, use this to narrow down which
|
||||
/// files will be matched to parse into the final [PeripheryConfig].
|
||||
@@ -95,7 +95,7 @@ pub struct Env {
|
||||
/// provided to `config_keywords` will be included.
|
||||
///
|
||||
/// Note. This is overridden if the equivalent arg is passed in [CliArgs].
|
||||
#[serde(default)]
|
||||
#[serde(default, alias = "periphery_config_keyword")]
|
||||
pub periphery_config_keywords: Vec<String>,
|
||||
|
||||
/// Will merge nested config object (eg. secrets, providers) across multiple
|
||||
|
||||
@@ -164,7 +164,11 @@ pub fn get_image_name(
|
||||
}
|
||||
|
||||
pub fn to_komodo_name(name: &str) -> String {
|
||||
name.to_lowercase().replace([' ', '.'], "_")
|
||||
name
|
||||
.to_lowercase()
|
||||
.replace([' ', '.'], "_")
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Unix timestamp in milliseconds as i64
|
||||
@@ -620,7 +624,7 @@ impl CloneArgs {
|
||||
access_token: Option<&str>,
|
||||
) -> anyhow::Result<String> {
|
||||
let access_token_at = match &access_token {
|
||||
Some(token) => format!("{token}@"),
|
||||
Some(token) => format!("token:{token}@"),
|
||||
None => String::new(),
|
||||
};
|
||||
let protocol = if self.https { "https" } else { "http" };
|
||||
|
||||
@@ -11,7 +11,7 @@ use typeshare::typeshare;
|
||||
use super::{
|
||||
docker::container::ContainerListItem,
|
||||
resource::{Resource, ResourceListItem, ResourceQuery},
|
||||
to_komodo_name, FileContents,
|
||||
to_komodo_name, FileContents, SystemCommand,
|
||||
};
|
||||
|
||||
#[typeshare]
|
||||
@@ -284,6 +284,12 @@ pub struct StackConfig {
|
||||
#[builder(default)]
|
||||
pub commit: String,
|
||||
|
||||
/// By default, the Stack will `git pull` the repo after it is first cloned.
|
||||
/// If this option is enabled, the repo folder will be deleted and recloned instead.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub reclone: bool,
|
||||
|
||||
/// Whether incoming webhooks actually trigger action.
|
||||
#[serde(default = "default_webhook_enabled")]
|
||||
#[builder(default = "default_webhook_enabled()")]
|
||||
@@ -312,6 +318,11 @@ pub struct StackConfig {
|
||||
#[builder(default)]
|
||||
pub registry_account: String,
|
||||
|
||||
/// The optional command to run before the Stack is deployed.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub pre_deploy: SystemCommand,
|
||||
|
||||
/// The extra arguments to pass after `docker compose up -d`.
|
||||
/// If empty, no extra arguments will be passed.
|
||||
#[serde(default)]
|
||||
@@ -410,6 +421,7 @@ impl Default for StackConfig {
|
||||
file_contents: Default::default(),
|
||||
auto_pull: default_auto_pull(),
|
||||
ignore_services: Default::default(),
|
||||
pre_deploy: Default::default(),
|
||||
extra_args: Default::default(),
|
||||
environment: Default::default(),
|
||||
env_file_path: default_env_file_path(),
|
||||
@@ -421,6 +433,7 @@ impl Default for StackConfig {
|
||||
repo: Default::default(),
|
||||
branch: default_branch(),
|
||||
commit: Default::default(),
|
||||
reclone: Default::default(),
|
||||
git_account: Default::default(),
|
||||
webhook_enabled: default_webhook_enabled(),
|
||||
webhook_secret: Default::default(),
|
||||
|
||||
@@ -27,8 +27,8 @@ pub fn parse_key_value_list(
|
||||
.trim_start_matches('-')
|
||||
.trim();
|
||||
// Remove wrapping quotes (from yaml list)
|
||||
let line = if let Some(line) = line.strip_prefix('"') {
|
||||
line.strip_suffix('"').unwrap_or(line)
|
||||
let line = if let Some(line) = line.strip_prefix(['"', '\'']) {
|
||||
line.strip_suffix(['"', '\'']).unwrap_or(line)
|
||||
} else {
|
||||
line
|
||||
};
|
||||
@@ -43,11 +43,12 @@ pub fn parse_key_value_list(
|
||||
.map(|(key, value)| {
|
||||
let value = value.trim();
|
||||
// Remove wrapping quotes around value
|
||||
if let Some(value) = value.strip_prefix('"') {
|
||||
value.strip_suffix('"').unwrap_or(value)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
let value =
|
||||
if let Some(value) = value.strip_prefix(['"', '\'']) {
|
||||
value.strip_suffix(['"', '\'']).unwrap_or(value)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
(key.trim().to_string(), value.trim().to_string())
|
||||
})?;
|
||||
anyhow::Ok((key, value))
|
||||
|
||||
@@ -438,8 +438,6 @@ export interface BuildConfig {
|
||||
webhook_secret?: string;
|
||||
/** The optional command run after repo clone and before docker build. */
|
||||
pre_build?: SystemCommand;
|
||||
/** Configuration for the registry to push the built image to. */
|
||||
image_registry?: ImageRegistryConfig;
|
||||
/**
|
||||
* The path of the docker build context relative to the root of the repo.
|
||||
* Default: "." (the root of the repo).
|
||||
@@ -447,6 +445,8 @@ export interface BuildConfig {
|
||||
build_path: string;
|
||||
/** The path of the dockerfile relative to the build path. */
|
||||
dockerfile_path: string;
|
||||
/** Configuration for the registry to push the built image to. */
|
||||
image_registry?: ImageRegistryConfig;
|
||||
/** Whether to skip secret interpolation in the build_args. */
|
||||
skip_secret_interp?: boolean;
|
||||
/** Whether to use buildx to build (eg `docker buildx build ...`) */
|
||||
@@ -512,6 +512,8 @@ export interface BuildListItemInfo {
|
||||
builder_id: string;
|
||||
/** The git provider domain */
|
||||
git_provider: string;
|
||||
/** The image registry domain */
|
||||
image_registry_domain: string;
|
||||
/** The repo used as the source of the build */
|
||||
repo: string;
|
||||
/** The branch of the repo */
|
||||
@@ -2365,6 +2367,11 @@ export interface StackConfig {
|
||||
branch: string;
|
||||
/** Optionally set a specific commit hash. */
|
||||
commit?: string;
|
||||
/**
|
||||
* By default, the Stack will `git pull` the repo after it is first cloned.
|
||||
* If this option is enabled, the repo folder will be deleted and recloned instead.
|
||||
*/
|
||||
reclone?: boolean;
|
||||
/** Whether incoming webhooks actually trigger action. */
|
||||
webhook_enabled: boolean;
|
||||
/**
|
||||
@@ -2378,6 +2385,8 @@ export interface StackConfig {
|
||||
registry_provider?: string;
|
||||
/** Used with `registry_provider` to login to a registry before docker compose up. */
|
||||
registry_account?: string;
|
||||
/** The optional command to run before the Stack is deployed. */
|
||||
pre_deploy?: SystemCommand;
|
||||
/**
|
||||
* The extra arguments to pass after `docker compose up -d`.
|
||||
* If empty, no extra arguments will be passed.
|
||||
|
||||
@@ -6,7 +6,7 @@ Komodo just needs a bit of information in order to build your image.
|
||||
Komodo supports cloning repos over http/s, from any provider that supports cloning private repos using `git clone https://<Token>@git-provider.net/<Owner>/<Repo>`.
|
||||
|
||||
Accounts / access tokens can be configured in either the [core config](../setup/advanced.mdx#mount-a-config-file)
|
||||
or in the [periphery config](../setup/connect-servers.mdx#manual-install-steps---binaries).
|
||||
or in the [periphery config](../connect-servers.mdx#manual-install-steps---binaries).
|
||||
|
||||
### Repo configuration
|
||||
To specify the git repo to build, just give it the name of the repo and the branch under *repo config*. The name is given like `mbecker20/komodo`, it includes the username / organization that owns the repo.
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
Connecting a server to Komodo has 2 steps:
|
||||
|
||||
1. Install the Periphery agent on the server
|
||||
2. Adding the server to Komodo via the core API
|
||||
1. Install the Periphery agent on the server (either binary or container).
|
||||
2. Add the server to Komodo via the Core API / UI.
|
||||
|
||||
Once step 1. is complete, you can just connect the server to Komodo Core from the UI.
|
||||
|
||||
## Install
|
||||
## Install Periphery
|
||||
|
||||
You can install Periphery as a systemd managed process, run it as a [docker container](https://github.com/mbecker20/komodo/pkgs/container/periphery), or do whatever you want with the binary.
|
||||
|
||||
@@ -16,7 +16,7 @@ const sidebars: SidebarsConfig = {
|
||||
"resources",
|
||||
{
|
||||
type: "category",
|
||||
label: "Setup Komodo",
|
||||
label: "Setup Komodo Core",
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "setup/index",
|
||||
@@ -26,9 +26,9 @@ const sidebars: SidebarsConfig = {
|
||||
"setup/postgres",
|
||||
"setup/sqlite",
|
||||
"setup/advanced",
|
||||
"setup/connect-servers",
|
||||
],
|
||||
},
|
||||
"connect-servers",
|
||||
{
|
||||
type: "category",
|
||||
label: "Build Images",
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
# React + TypeScript + Vite
|
||||
# Komodo Frontend
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
Komodo JS stack uses Yarn + Vite + React + Tailwind + shadcn/ui
|
||||
|
||||
Currently, two official plugins are available:
|
||||
## Setup Dev Environment
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
The frontend depends on the local package `@komodo/client` located at `/client/core/ts`.
|
||||
This must first be built and prepared for yarn link.
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
The following command should setup everything up (run with /frontend as working directory):
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
```sh
|
||||
cd ../client/core/ts && yarn && yarn build && yarn link && \
|
||||
cd ../../../frontend && yarn link @komodo/client && yarn
|
||||
```
|
||||
|
||||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
||||
You can make a new file `.env.development` (gitignored) which holds:
|
||||
```sh
|
||||
VITE_KOMODO_HOST=https://demo.komo.do
|
||||
```
|
||||
You can point it to any Komodo host you like, including the demo.
|
||||
|
||||
Now you can start the dev frontend server:
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ConfirmUpdate,
|
||||
} from "@components/config/util";
|
||||
import { Section } from "@components/layouts";
|
||||
import { MonacoLanguage } from "@components/monaco";
|
||||
import { Types } from "@komodo/client";
|
||||
import { cn } from "@lib/utils";
|
||||
import { Button } from "@ui/button";
|
||||
@@ -31,6 +32,7 @@ export const ConfigLayout = <
|
||||
onReset,
|
||||
selector,
|
||||
titleOther,
|
||||
file_contents_language,
|
||||
}: {
|
||||
original: T;
|
||||
config: Partial<T>;
|
||||
@@ -40,6 +42,7 @@ export const ConfigLayout = <
|
||||
onReset: () => void;
|
||||
selector?: ReactNode;
|
||||
titleOther?: ReactNode;
|
||||
file_contents_language?: MonacoLanguage;
|
||||
}) => {
|
||||
const titleProps = titleOther
|
||||
? { titleOther }
|
||||
@@ -74,6 +77,7 @@ export const ConfigLayout = <
|
||||
content={config}
|
||||
onConfirm={onConfirm}
|
||||
disabled={disabled}
|
||||
file_contents_language={file_contents_language}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -120,6 +124,7 @@ export const Config = <T,>({
|
||||
components,
|
||||
selector,
|
||||
titleOther,
|
||||
file_contents_language,
|
||||
}: {
|
||||
resource_id: string;
|
||||
resource_type: Types.ResourceTarget["type"];
|
||||
@@ -134,6 +139,7 @@ export const Config = <T,>({
|
||||
string, // sidebar key
|
||||
ConfigComponent<T>[] | false | undefined
|
||||
>;
|
||||
file_contents_language?: MonacoLanguage;
|
||||
}) => {
|
||||
// let component_keys = keys(components);
|
||||
// const [_show, setShow] = useLocalStorage(
|
||||
@@ -164,6 +170,7 @@ export const Config = <T,>({
|
||||
}}
|
||||
onReset={() => set({})}
|
||||
selector={selector}
|
||||
file_contents_language={file_contents_language}
|
||||
>
|
||||
<div className="flex gap-6">
|
||||
<div className="hidden xl:block relative pr-6 border-r">
|
||||
@@ -186,7 +193,7 @@ export const Config = <T,>({
|
||||
key={section + item.label}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
className="justify-end w-full"
|
||||
size="sm"
|
||||
>
|
||||
|
||||
@@ -46,7 +46,11 @@ import {
|
||||
soft_text_color_class_by_intention,
|
||||
text_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { MonacoDiffEditor, MonacoEditor } from "@components/monaco";
|
||||
import {
|
||||
MonacoDiffEditor,
|
||||
MonacoEditor,
|
||||
MonacoLanguage,
|
||||
} from "@components/monaco";
|
||||
|
||||
export const ConfigItem = ({
|
||||
label,
|
||||
@@ -398,6 +402,7 @@ export const AccountSelector = ({
|
||||
provider,
|
||||
selected,
|
||||
onSelect,
|
||||
placeholder = "Select Account",
|
||||
}: {
|
||||
disabled: boolean;
|
||||
type: "Server" | "Builder" | "None";
|
||||
@@ -406,6 +411,7 @@ export const AccountSelector = ({
|
||||
provider: string;
|
||||
selected: string | undefined;
|
||||
onSelect: (id: string) => void;
|
||||
placeholder?: string;
|
||||
}) => {
|
||||
const [db_request, config_request]:
|
||||
| ["ListGitProviderAccounts", "ListGitProvidersFromConfig"]
|
||||
@@ -447,7 +453,7 @@ export const AccountSelector = ({
|
||||
className="w-full lg:w-[200px] max-w-[50%]"
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectValue placeholder="Select Account" />
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={"Empty"}>None</SelectItem>
|
||||
@@ -471,12 +477,16 @@ export const AccountSelectorConfig = (params: {
|
||||
provider: string;
|
||||
selected: string | undefined;
|
||||
onSelect: (id: string) => void;
|
||||
placeholder: string;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
}) => {
|
||||
return (
|
||||
<ConfigItem
|
||||
label="Account"
|
||||
description="Select the account used to log in to the provider"
|
||||
description={
|
||||
params.description ??
|
||||
"Select the account used to log in to the provider"
|
||||
}
|
||||
>
|
||||
<AccountSelector {...params} />
|
||||
</ConfigItem>
|
||||
@@ -569,6 +579,8 @@ interface ConfirmUpdateProps<T> {
|
||||
content: Partial<T>;
|
||||
onConfirm: () => void;
|
||||
disabled: boolean;
|
||||
language?: MonacoLanguage;
|
||||
file_contents_language?: MonacoLanguage;
|
||||
}
|
||||
|
||||
export function ConfirmUpdate<T>({
|
||||
@@ -576,6 +588,8 @@ export function ConfirmUpdate<T>({
|
||||
content,
|
||||
onConfirm,
|
||||
disabled,
|
||||
language,
|
||||
file_contents_language,
|
||||
}: ConfirmUpdateProps<T>) {
|
||||
const [open, set] = useState(false);
|
||||
useCtrlKeyListener("s", () => {
|
||||
@@ -608,6 +622,8 @@ export function ConfirmUpdate<T>({
|
||||
_key={key as any}
|
||||
val={val as any}
|
||||
previous={previous}
|
||||
language={language}
|
||||
file_contents_language={file_contents_language}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -629,23 +645,24 @@ function ConfirmUpdateItem<T>({
|
||||
_key,
|
||||
val: _val,
|
||||
previous,
|
||||
language,
|
||||
file_contents_language,
|
||||
}: {
|
||||
_key: keyof T;
|
||||
val: T[keyof T];
|
||||
previous: T;
|
||||
language?: MonacoLanguage;
|
||||
file_contents_language?: MonacoLanguage;
|
||||
}) {
|
||||
const [show, setShow] = useState(true);
|
||||
const val =
|
||||
typeof _val === "string"
|
||||
? _key === "environment" ||
|
||||
_key === "build_args" ||
|
||||
_key === "secret_args"
|
||||
? _val
|
||||
.split("\n")
|
||||
.filter((line) => !line.startsWith("#"))
|
||||
.map((line) => line.split(" #")[0])
|
||||
.join("\n")
|
||||
: _val
|
||||
? _val
|
||||
: Array.isArray(_val)
|
||||
? _val.length > 0 &&
|
||||
["string", "number", "boolean"].includes(typeof _val[0])
|
||||
? JSON.stringify(_val)
|
||||
: JSON.stringify(_val, null, 2)
|
||||
: JSON.stringify(_val, null, 2);
|
||||
const prev_val =
|
||||
typeof previous[_key] === "string"
|
||||
@@ -653,7 +670,12 @@ function ConfirmUpdateItem<T>({
|
||||
: _key === "environment" ||
|
||||
_key === "build_args" ||
|
||||
_key === "secret_args"
|
||||
? env_to_text(previous[_key] as any) ?? ""
|
||||
? env_to_text(previous[_key] as any) ?? "" // For backward compat with 1.14
|
||||
: Array.isArray(previous[_key])
|
||||
? previous[_key].length > 0 &&
|
||||
["string", "number", "boolean"].includes(typeof previous[_key][0])
|
||||
? JSON.stringify(previous[_key])
|
||||
: JSON.stringify(previous[_key], null, 2)
|
||||
: JSON.stringify(previous[_key], null, 2);
|
||||
const showDiff =
|
||||
val?.includes("\n") ||
|
||||
@@ -676,7 +698,16 @@ function ConfirmUpdateItem<T>({
|
||||
<MonacoDiffEditor
|
||||
original={prev_val}
|
||||
modified={val}
|
||||
language="toml"
|
||||
language={
|
||||
language ??
|
||||
(["environment", "build_args", "secret_args"].includes(
|
||||
_key as string
|
||||
)
|
||||
? "key_value"
|
||||
: _key === "file_contents"
|
||||
? file_contents_language
|
||||
: "json")
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<pre style={{ minHeight: 0 }}>
|
||||
|
||||
@@ -203,13 +203,13 @@ export const NewLayout = ({
|
||||
entityType,
|
||||
children,
|
||||
enabled,
|
||||
onSuccess,
|
||||
onConfirm,
|
||||
onOpenChange,
|
||||
}: {
|
||||
entityType: string;
|
||||
children: ReactNode;
|
||||
enabled: boolean;
|
||||
onSuccess: () => Promise<unknown>;
|
||||
onConfirm: () => Promise<unknown>;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}) => {
|
||||
const [open, set] = useState(false);
|
||||
@@ -237,7 +237,7 @@ export const NewLayout = ({
|
||||
variant="outline"
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await onSuccess();
|
||||
await onConfirm();
|
||||
setLoading(false);
|
||||
set(false);
|
||||
}}
|
||||
|
||||
@@ -115,9 +115,17 @@ export const BuildComponents: RequiredResourceComponents = {
|
||||
|
||||
New: () => {
|
||||
const user = useUser().data;
|
||||
const builders = useRead("ListBuilders", {}).data;
|
||||
if (!user) return null;
|
||||
if (!user.admin && !user.create_build_permissions) return null;
|
||||
return <NewResource type="Build" />;
|
||||
return (
|
||||
<NewResource
|
||||
type="Build"
|
||||
builder_id={
|
||||
builders && builders.length === 1 ? builders[0].id : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
Table: ({ resources }) => (
|
||||
|
||||
@@ -74,7 +74,7 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<NewLayout
|
||||
entityType="Builder"
|
||||
onSuccess={async () => {
|
||||
onConfirm={async () => {
|
||||
if (!type) return;
|
||||
const id = (await mutateAsync({ name, config: { type, params: {} } }))
|
||||
._id?.$oid!;
|
||||
|
||||
@@ -119,7 +119,11 @@ export const ResourceSelector = ({
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="secondary" className="flex justify-start gap-2 w-fit max-w-[200px]" disabled={disabled}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex justify-start gap-2 w-fit max-w-[200px]"
|
||||
disabled={disabled}
|
||||
>
|
||||
{name || `Select ${type}`}
|
||||
{!disabled && <ChevronsUpDown className="w-3 h-3" />}
|
||||
</Button>
|
||||
@@ -258,16 +262,19 @@ export const NewResource = ({
|
||||
type,
|
||||
readable_type,
|
||||
server_id,
|
||||
builder_id,
|
||||
build_id,
|
||||
name: _name = "",
|
||||
}: {
|
||||
type: UsableResource;
|
||||
readable_type?: string;
|
||||
server_id?: string;
|
||||
builder_id?: string;
|
||||
build_id?: string;
|
||||
name?: string;
|
||||
}) => {
|
||||
const nav = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const { mutateAsync } = useWrite(`Create${type}`);
|
||||
const [name, setName] = useState(_name);
|
||||
const type_display =
|
||||
@@ -276,7 +283,7 @@ export const NewResource = ({
|
||||
: type === "ResourceSync"
|
||||
? "resource-sync"
|
||||
: type.toLowerCase();
|
||||
const config: Types._PartialDeploymentConfig =
|
||||
const config: Types._PartialDeploymentConfig | Types._PartialRepoConfig =
|
||||
type === "Deployment"
|
||||
? {
|
||||
server_id,
|
||||
@@ -287,15 +294,19 @@ export const NewResource = ({
|
||||
: type === "Stack"
|
||||
? { server_id }
|
||||
: type === "Repo"
|
||||
? { server_id }
|
||||
? { server_id, builder_id }
|
||||
: type === "Build"
|
||||
? { builder_id }
|
||||
: {};
|
||||
const onConfirm = async () => {
|
||||
if (!name) toast({ title: "Name cannot be empty" });
|
||||
const id = (await mutateAsync({ name, config }))._id?.$oid!;
|
||||
nav(`/${usableResourcePath(type)}/${id}`);
|
||||
};
|
||||
return (
|
||||
<NewLayout
|
||||
entityType={readable_type ?? type}
|
||||
onSuccess={async () => {
|
||||
const id = (await mutateAsync({ name, config }))._id?.$oid!;
|
||||
nav(`/${usableResourcePath(type)}/${id}`);
|
||||
}}
|
||||
onConfirm={onConfirm}
|
||||
enabled={!!name}
|
||||
onOpenChange={() => setName("")}
|
||||
>
|
||||
@@ -305,6 +316,12 @@ export const NewResource = ({
|
||||
placeholder={`${type_display}-name`}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (!name) return;
|
||||
if (e.key === "Enter") {
|
||||
onConfirm();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</NewLayout>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { ReactNode, useState } from "react";
|
||||
import {
|
||||
AccountSelectorConfig,
|
||||
AddExtraArgMenu,
|
||||
ConfigItem,
|
||||
ConfigList,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
DefaultTerminationSignal,
|
||||
TerminationTimeout,
|
||||
} from "./components/term-signal";
|
||||
import { extract_registry_domain } from "@lib/utils";
|
||||
|
||||
export const DeploymentConfig = ({
|
||||
id,
|
||||
@@ -31,6 +33,7 @@ export const DeploymentConfig = ({
|
||||
target: { type: "Deployment", id },
|
||||
}).data;
|
||||
const config = useRead("GetDeployment", { deployment: id }).data?.config;
|
||||
const builds = useRead("ListBuilds", {}).data;
|
||||
const global_disabled =
|
||||
useRead("GetCoreInfo", {}).data?.ui_write_disabled ?? false;
|
||||
const [update, set] = useState<Partial<Types.DeploymentConfig>>({});
|
||||
@@ -100,6 +103,37 @@ export const DeploymentConfig = ({
|
||||
image: (value, set) => (
|
||||
<ImageConfig image={value} set={set} disabled={disabled} />
|
||||
),
|
||||
image_registry_account: (account, set) => {
|
||||
const image = update.image ?? config.image;
|
||||
const provider =
|
||||
image?.type === "Image" && image.params.image
|
||||
? extract_registry_domain(image.params.image)
|
||||
: image?.type === "Build" && image.params.build_id
|
||||
? builds?.find((b) => b.id === image.params.build_id)?.info
|
||||
.image_registry_domain
|
||||
: undefined;
|
||||
return (
|
||||
<AccountSelectorConfig
|
||||
id={update.server_id ?? config.server_id ?? undefined}
|
||||
type="Server"
|
||||
account_type="docker"
|
||||
provider={provider ?? "docker.io"}
|
||||
selected={account}
|
||||
onSelect={(image_registry_account) =>
|
||||
set({ image_registry_account })
|
||||
}
|
||||
disabled={disabled}
|
||||
placeholder={
|
||||
image?.type === "Build" ? "Same as Build" : undefined
|
||||
}
|
||||
description={
|
||||
image?.type === "Build"
|
||||
? "Select an alternate account used to log in to the provider"
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
redeploy_on_build: (update.image?.type ?? config.image?.type) ===
|
||||
"Build" && {
|
||||
description: "Automatically redeploy when the image is built.",
|
||||
@@ -182,6 +216,53 @@ export const DeploymentConfig = ({
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Restart",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
restart: (value, set) => (
|
||||
<RestartModeSelector
|
||||
selected={value}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
advanced: [
|
||||
{
|
||||
label: "Command",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
command: (value, set) => (
|
||||
<ConfigItem
|
||||
label="Command"
|
||||
description={
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
<div>Replace the CMD, or extend the ENTRYPOINT.</div>
|
||||
<Link
|
||||
to="https://docs.docker.com/engine/reference/run/#commands-and-arguments"
|
||||
target="_blank"
|
||||
className="text-primary"
|
||||
>
|
||||
See docker docs.
|
||||
{/* <Button variant="link" className="p-0">
|
||||
</Button> */}
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<MonacoEditor
|
||||
value={value}
|
||||
language="shell"
|
||||
onValueChange={(command) => set({ command })}
|
||||
readOnly={disabled}
|
||||
/>
|
||||
</ConfigItem>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Labels",
|
||||
description: "Attach --labels to the container.",
|
||||
@@ -197,19 +278,6 @@ export const DeploymentConfig = ({
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Restart",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
restart: (value, set) => (
|
||||
<RestartModeSelector
|
||||
selected={value}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Extra Args",
|
||||
labelHidden: true,
|
||||
@@ -255,38 +323,6 @@ export const DeploymentConfig = ({
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Command",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
command: (value, set) => (
|
||||
<ConfigItem
|
||||
label="Command"
|
||||
description={
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
<div>Replace the CMD, or extend the ENTRYPOINT.</div>
|
||||
<Link
|
||||
to="https://docs.docker.com/engine/reference/run/#commands-and-arguments"
|
||||
target="_blank"
|
||||
className="text-primary"
|
||||
>
|
||||
See docker docs.
|
||||
{/* <Button variant="link" className="p-0">
|
||||
</Button> */}
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<MonacoEditor
|
||||
value={value}
|
||||
language="shell"
|
||||
onValueChange={(command) => set({ command })}
|
||||
readOnly={disabled}
|
||||
/>
|
||||
</ConfigItem>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Termination",
|
||||
boldLabel: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useLocalStorage, useRead } from "@lib/hooks";
|
||||
import { Types } from "@komodo/client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { AlertTriangle, HardDrive, Rocket, Server } from "lucide-react";
|
||||
import { HardDrive, Rocket, Server } from "lucide-react";
|
||||
import { cn } from "@lib/utils";
|
||||
import { useServer } from "../server";
|
||||
import {
|
||||
@@ -22,7 +22,6 @@ import { DeleteResource, NewResource, ResourceLink } from "../common";
|
||||
import { RunBuild } from "../build/actions";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
|
||||
import { DeploymentConfig } from "./config";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DashboardPieChart } from "@pages/home/dashboard";
|
||||
import { ResourcePageHeader, StatusBadge } from "@components/util";
|
||||
|
||||
@@ -145,9 +144,21 @@ export const DeploymentComponents: RequiredResourceComponents = {
|
||||
);
|
||||
},
|
||||
|
||||
New: ({ server_id, build_id }) => (
|
||||
<NewResource type="Deployment" server_id={server_id} build_id={build_id} />
|
||||
),
|
||||
New: ({ server_id: _server_id, build_id }) => {
|
||||
const servers = useRead("ListServers", {}).data;
|
||||
const server_id = _server_id
|
||||
? _server_id
|
||||
: servers && servers.length === 1
|
||||
? servers[0].id
|
||||
: undefined;
|
||||
return (
|
||||
<NewResource
|
||||
type="Deployment"
|
||||
server_id={server_id}
|
||||
build_id={build_id}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
Table: ({ resources }) => {
|
||||
return (
|
||||
@@ -192,17 +203,6 @@ export const DeploymentComponents: RequiredResourceComponents = {
|
||||
</div>
|
||||
);
|
||||
},
|
||||
Alerts: ({ id }) => {
|
||||
return (
|
||||
<Link
|
||||
to={`/deployments/${id}/alerts`}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
Alerts
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
Actions: {
|
||||
|
||||
@@ -57,6 +57,7 @@ export const ResourceSyncConfig = ({
|
||||
onSave={async () => {
|
||||
await mutateAsync({ id, config: update });
|
||||
}}
|
||||
file_contents_language="toml"
|
||||
components={{
|
||||
"": [
|
||||
{
|
||||
|
||||
@@ -58,7 +58,7 @@ export const ServerTemplateComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<NewLayout
|
||||
entityType="Server Template"
|
||||
onSuccess={async () => {
|
||||
onConfirm={async () => {
|
||||
if (!type) return;
|
||||
const id = (await mutateAsync({ name, config: { type, params: {} } }))
|
||||
._id?.$oid!;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ConfigList,
|
||||
InputList,
|
||||
ProviderSelectorConfig,
|
||||
SystemCommand,
|
||||
} from "@components/config/util";
|
||||
import { Types } from "@komodo/client";
|
||||
import { useInvalidate, useLocalStorage, useRead, useWrite } from "@lib/hooks";
|
||||
@@ -233,10 +234,24 @@ export const StackConfig = ({
|
||||
placeholder: "Compose project name",
|
||||
boldLabel: true,
|
||||
description:
|
||||
"Optionally set a different compose project name. It should match the compose project name on your host.",
|
||||
"Optionally set a different compose project name. If importing existing stack, this should match the compose project name on your host.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Pre Deploy",
|
||||
description:
|
||||
"Execute a shell command before running docker compose up. The 'path' is relative to the Run Directory",
|
||||
components: {
|
||||
pre_deploy: (value, set) => (
|
||||
<SystemCommand
|
||||
value={value}
|
||||
set={(value) => set({ pre_deploy: value })}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Extra Args",
|
||||
labelHidden: true,
|
||||
@@ -467,6 +482,10 @@ export const StackConfig = ({
|
||||
description:
|
||||
"Switch to a specific hash after cloning the branch.",
|
||||
},
|
||||
reclone: {
|
||||
description:
|
||||
"Delete the repo folder and clone it again, instead of using 'git pull'.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -718,6 +737,7 @@ export const StackConfig = ({
|
||||
await mutateAsync({ id, config: update });
|
||||
}}
|
||||
components={components}
|
||||
file_contents_language="yaml"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -151,7 +151,15 @@ export const StackComponents: RequiredResourceComponents = {
|
||||
);
|
||||
},
|
||||
|
||||
New: ({ server_id }) => <NewResource type="Stack" server_id={server_id} />,
|
||||
New: ({ server_id: _server_id }) => {
|
||||
const servers = useRead("ListServers", {}).data;
|
||||
const server_id = _server_id
|
||||
? _server_id
|
||||
: servers && servers.length === 1
|
||||
? servers[0].id
|
||||
: undefined;
|
||||
return <NewResource type="Stack" server_id={server_id} />;
|
||||
},
|
||||
|
||||
Table: ({ resources }) => (
|
||||
<StackTable stacks={resources as Types.StackListItem[]} />
|
||||
|
||||
@@ -80,7 +80,7 @@ export const StackInfo = ({
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
{/* Update deployed contents with diff */}
|
||||
{/* {!is_down && deployed_contents.length > 0 && (
|
||||
<Card>
|
||||
@@ -189,6 +189,7 @@ export const StackInfo = ({
|
||||
}
|
||||
}}
|
||||
disabled={!edits[content.path]}
|
||||
language="yaml"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const NewUserGroup = () => {
|
||||
return (
|
||||
<NewLayout
|
||||
entityType="User Group"
|
||||
onSuccess={() => mutateAsync({ name })}
|
||||
onConfirm={() => mutateAsync({ name })}
|
||||
enabled={!!name}
|
||||
onOpenChange={() => setName("")}
|
||||
>
|
||||
@@ -46,7 +46,7 @@ export const NewServiceUser = () => {
|
||||
return (
|
||||
<NewLayout
|
||||
entityType="Service User"
|
||||
onSuccess={() => mutateAsync({ username, description: "" })}
|
||||
onConfirm={() => mutateAsync({ username, description: "" })}
|
||||
enabled={!!username}
|
||||
onOpenChange={() => setUsername("")}
|
||||
>
|
||||
|
||||
@@ -244,3 +244,13 @@ export const is_service_user = (user_id: string) => {
|
||||
user_id === "Repo Manager"
|
||||
);
|
||||
};
|
||||
|
||||
export const extract_registry_domain = (image_name: string) => {
|
||||
if (!image_name) return "docker.io";
|
||||
const maybe_domain = image_name.split("/")[0];
|
||||
if (maybe_domain.includes(".")) {
|
||||
return maybe_domain
|
||||
} else {
|
||||
return "docker.io"
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ where
|
||||
};
|
||||
|
||||
if let Some(command) = args.on_clone {
|
||||
if !command.path.is_empty() && !command.command.is_empty() {
|
||||
if !command.command.is_empty() {
|
||||
let on_clone_path = repo_dir.join(&command.path);
|
||||
if let Some(secrets) = secrets {
|
||||
let (full_command, mut replacers) =
|
||||
@@ -154,7 +154,7 @@ where
|
||||
}
|
||||
}
|
||||
if let Some(command) = args.on_pull {
|
||||
if !command.path.is_empty() && !command.command.is_empty() {
|
||||
if !command.command.is_empty() {
|
||||
let on_pull_path = repo_dir.join(&command.path);
|
||||
if let Some(secrets) = secrets {
|
||||
let (full_command, mut replacers) =
|
||||
|
||||
@@ -30,6 +30,10 @@ docker compose -p komodo-dev -f test.compose.yaml build"""
|
||||
description = "runs core --release pointing to test.core.config.toml"
|
||||
cmd = "KOMODO_CONFIG_PATH=test.core.config.toml cargo run -p komodo_core --release"
|
||||
|
||||
[test-periphery]
|
||||
description = "runs periphery --release pointing to test.periphery.config.toml"
|
||||
cmd = "PERIPHERY_CONFIG_PATH=test.periphery.config.toml cargo run -p komodo_periphery --release"
|
||||
|
||||
[docsite-start]
|
||||
path = "docsite"
|
||||
cmd = "yarn start"
|
||||
@@ -38,5 +42,5 @@ cmd = "yarn start"
|
||||
path = "docsite"
|
||||
cmd = "yarn deploy"
|
||||
|
||||
[rustdoc-server]
|
||||
cmd = "cargo watch -s 'cargo doc --no-deps -p komodo_client' & http --quiet target/doc"
|
||||
# [rustdoc-server]
|
||||
# cmd = "cargo watch -s 'cargo doc --no-deps -p komodo_client' & http --quiet target/doc"
|
||||
@@ -25,9 +25,6 @@ Will install to paths:
|
||||
|
||||
*Note*. The user running periphery must be a member of the docker group, in order to use the docker cli without sudo.
|
||||
|
||||
*Note*. Ensure the user running periphery has write access to the configure [repo directory](https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml).
|
||||
This allows periphery to clone repos and write compose files.
|
||||
|
||||
```sh
|
||||
curl -sSL https://raw.githubusercontent.com/mbecker20/komodo/main/scripts/setup-periphery.py | python3 - --user
|
||||
```
|
||||
@@ -35,4 +32,17 @@ curl -sSL https://raw.githubusercontent.com/mbecker20/komodo/main/scripts/setup-
|
||||
Will install to paths:
|
||||
- periphery (binary) -> `$HOME/.local/bin`
|
||||
- periphery.service -> `$HOME/.config/systemd/user/periphery.service`
|
||||
- periphery.config.toml -> `$HOME/.config/komodo/periphery.config.toml`
|
||||
- periphery.config.toml -> `$HOME/.config/komodo/periphery.config.toml`
|
||||
|
||||
*Note*. Ensure the user running periphery has write permissions to the configured folders `repo_dir`, `stack_dir`, and `ssl_key_file` / `ssl_cert_file` parent folder.
|
||||
This allows periphery to clone repos, write compose files, and generate ssl certs.
|
||||
|
||||
For example in `periphery.config.toml`, running under `ubuntu` user:
|
||||
```toml
|
||||
repo_dir = "/home/ubuntu/.komodo/repos"
|
||||
stack_dir = "/home/ubuntu/.komodo/stacks"
|
||||
|
||||
ssl_enabled = true
|
||||
ssl_key_file = "/home/ubuntu/.komodo/ssl/key.pem"
|
||||
ssl_cert_file = "/home/ubuntu/.komodo/ssl/cert.pem"
|
||||
```
|
||||
@@ -1,14 +1,18 @@
|
||||
###########################
|
||||
# 🦎 KOMODO CORE CONFIG 🦎 #
|
||||
###########################
|
||||
title = "Komodo Test"
|
||||
host = "http://localhost:9121"
|
||||
port = 9121
|
||||
passkey = "a_random_passkey"
|
||||
repo_directory = "/Users/max/komodo-repos"
|
||||
repo_directory = ".core-repos"
|
||||
frontend_path = "frontend/dist"
|
||||
first_server = "http://localhost:8121"
|
||||
|
||||
disable_confirm_dialog = true
|
||||
enable_new_users = true
|
||||
|
||||
# database.address = "localhost:27017"
|
||||
# database.address = "ferretdb.komodo.orb.local"
|
||||
# database.address = "ferretdb.komodo-dev.orb.local"
|
||||
database.address = "test-ferretdb.orb.local"
|
||||
# database.username = "admin"
|
||||
# database.password = "admin"
|
||||
@@ -18,16 +22,20 @@ local_auth = true
|
||||
jwt_secret = "your_random_secret"
|
||||
jwt_ttl = "2-wk"
|
||||
|
||||
oidc_enabled = true
|
||||
oidc_provider = "http://server.authentik2.orb.local:9000/application/o/komodo"
|
||||
oidc_client_id = "komodo"
|
||||
oidc_client_secret = "komodo"
|
||||
oidc_enabled = false
|
||||
# oidc_provider = "http://server.authentik2.orb.local:9000/application/o/komodo"
|
||||
# oidc_client_id = "komodo"
|
||||
# oidc_client_secret = "komodo"
|
||||
|
||||
webhook_secret = "a_random_webhook_secret"
|
||||
|
||||
stack_poll_interval = "1-min"
|
||||
sync_poll_interval = "1-min"
|
||||
build_poll_interval = "1-min"
|
||||
repo_poll_interval = "1-min"
|
||||
monitoring_interval = "5-sec"
|
||||
resource_poll_interval = "1-min"
|
||||
|
||||
###########
|
||||
# LOGGING #
|
||||
###########
|
||||
logging.level = "info"
|
||||
logging.stdio = "standard"
|
||||
logging.otlp_endpoint = "http://tempo.grafana.orb.local:4317"
|
||||
logging.opentelemetry_service_name = "Komodo"
|
||||
|
||||
24
test.periphery.config.toml
Normal file
24
test.periphery.config.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
################################
|
||||
# 🦎 KOMODO PERIPHERY CONFIG 🦎 #
|
||||
################################
|
||||
port = 8121
|
||||
repo_dir = ".repos"
|
||||
stack_dir = ".stacks"
|
||||
stats_polling_rate = "1-sec"
|
||||
legacy_compose_cli = false
|
||||
include_disk_mounts = []
|
||||
|
||||
############
|
||||
# Security #
|
||||
############
|
||||
ssl_enabled = false
|
||||
ssl_key_file = ".ssl/key.pem"
|
||||
ssl_cert_file = ".ssl/cert.pem"
|
||||
|
||||
###########
|
||||
# LOGGING #
|
||||
###########
|
||||
logging.level = "info"
|
||||
logging.stdio = "standard"
|
||||
logging.otlp_endpoint = "http://tempo.grafana.orb.local:4317"
|
||||
logging.opentelemetry_service_name = "Periphery"
|
||||
Reference in New Issue
Block a user