* fix parser support single quote '

* add stack reclone toggle

* git clone with token uses token:<TOKEN> for gitlab compatability

* support stack pre deploy shell command

* rename compose down update log stage

* deployment configure registry login account

* local testing setup

* bump version to 1.15.3

* new resources auto assign server if only one

* better error log when try to create resource with duplicate name

* end description with .

* ConfirmUpdate multi language

* fix compose write to host logic

* improve instrumentation

* improve update diff when small array

improve 2

* fix compose env file passing when repo_dir is not absolute
This commit is contained in:
Maxwell Becker
2024-10-09 09:07:38 +03:00
committed by GitHub
parent 80e5d2a972
commit 2cfae525e9
40 changed files with 633 additions and 286 deletions

View File

@@ -6,3 +6,9 @@ LICENSE
*/node_modules
*/dist
creds.toml
.core-repos
.repos
.stacks
.ssl

11
.gitignore vendored
View File

@@ -1,11 +1,14 @@
target
/frontend/build
node_modules
/lib/ts_client/build
node_modules
dist
.env
.env.development
creds.toml
.syncs
.stacks
.DS_Store
creds.toml
.core-repos
.repos
.stacks
.ssl

27
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -10,18 +10,21 @@ 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 span =
info_span!("send_alerts", alerts = format!("{alerts:?}"));
async {
let Ok(alerters) = find_collect(
&db_client().alerters,
doc! { "config.enabled": true },
@@ -40,6 +43,9 @@ pub async fn send_alerts(alerts: &[Alert]) {
alerts.iter().map(|alert| send_alert(&alerters, alert));
join_all(handles).await;
}
.instrument(span)
.await
}
#[instrument(level = "debug")]

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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?;

View File

@@ -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

View File

@@ -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>();

View File

@@ -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,

View File

@@ -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,12 +432,24 @@ async fn write_stack(
}
};
let RepoActionResponse {
logs,
commit_hash,
commit_message,
env_file_path,
} = match State
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,
@@ -382,7 +464,14 @@ async fn write_stack(
(),
)
.await
{
};
let RepoActionResponse {
logs,
commit_hash,
commit_message,
env_file_path,
} = 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;

View File

@@ -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,12 +41,11 @@ 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
@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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" };

View File

@@ -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(),

View File

@@ -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,8 +43,9 @@ 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)
let value =
if let Some(value) = value.strip_prefix(['"', '\'']) {
value.strip_suffix(['"', '\'']).unwrap_or(value)
} else {
value
};

View File

@@ -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.

View File

@@ -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">

View File

@@ -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
: 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 }}>

View File

@@ -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);
}}

View File

@@ -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 }) => (

View File

@@ -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!;

View File

@@ -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>

View File

@@ -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,

View File

@@ -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: {

View File

@@ -57,6 +57,7 @@ export const ResourceSyncConfig = ({
onSave={async () => {
await mutateAsync({ id, config: update });
}}
file_contents_language="toml"
components={{
"": [
{

View File

@@ -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!;

View File

@@ -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"
/>
);
};

View File

@@ -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[]} />

View File

@@ -189,6 +189,7 @@ export const StackInfo = ({
}
}}
disabled={!edits[content.path]}
language="yaml"
/>
</div>
)}

View File

@@ -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("")}
>

View File

@@ -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"
}
}

View File

@@ -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) =

View File

@@ -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"

View File

@@ -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"

View 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"