Compare commits

..

1 Commits

Author SHA1 Message Date
Maxwell Becker
8ee270d045 1.15.4 (#114)
* stack destroy before deploy option

* add timestamps. Fix log polling even when poll not selected

* Add build [[$VERSION]] support. VERSION build arg default

* fix clippy lint

* initialize `first_builder`

* run_komodo_command uses parse_multiline_command

* comment UI for $VERSION and new command feature

* bump some deps

* support multiline commands in pre_deploy / pre_build
2024-10-10 00:37:23 -07:00
40 changed files with 541 additions and 273 deletions

106
Cargo.lock generated
View File

@@ -41,7 +41,7 @@ dependencies = [
[[package]]
name = "alerter"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"axum",
@@ -201,9 +201,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "aws-config"
version = "1.5.7"
version = "1.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8191fb3091fa0561d1379ef80333c3c7191c6f0435d986e85821bcf7acbd1126"
checksum = "7198e6f03240fdceba36656d8be440297b6b82270325908c7381f37d826a74f6"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -268,9 +268,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ec2"
version = "1.75.0"
version = "1.77.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6787d920877cca6a4ee3953093f6a47cefe26de95a4f7b3681c5850bfe657b4"
checksum = "4bb6f841697b994ec3a020c560b52693bc9fcb7b9c69210088ab56e03df23b5e"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -292,9 +292,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.44.0"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b90cfe6504115e13c41d3ea90286ede5aa14da294f3fe077027a6e83850843c"
checksum = "e33ae899566f3d395cbf42858e433930682cc9c1889fa89318896082fef45efb"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -314,9 +314,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.45.0"
version = "1.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167c0fad1f212952084137308359e8e4c4724d1c643038ce163f06de9662c1d0"
checksum = "f39c09e199ebd96b9f860b0fce4b6625f211e064ad7c8693b72ecf7ef03881e0"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -336,9 +336,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.44.0"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb5f98188ec1435b68097daa2a37d74b9d17c9caa799466338a8d1544e71b9d"
checksum = "3d95f93a98130389eb6233b9d615249e543f6c24a68ca1f109af9ca5164a8765"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -887,9 +887,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.19"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [
"clap_builder",
"clap_derive",
@@ -897,9 +897,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.19"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [
"anstream",
"anstyle",
@@ -943,7 +943,7 @@ dependencies = [
[[package]]
name = "command"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"komodo_client",
"run_command",
@@ -1149,18 +1149,18 @@ dependencies = [
[[package]]
name = "derive_builder"
version = "0.20.1"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.1"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
@@ -1170,9 +1170,9 @@ dependencies = [
[[package]]
name = "derive_builder_macro"
version = "0.20.1"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.77",
@@ -1355,7 +1355,7 @@ dependencies = [
[[package]]
name = "environment_file"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"thiserror",
]
@@ -1439,7 +1439,7 @@ dependencies = [
[[package]]
name = "formatting"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"serror",
]
@@ -1452,9 +1452,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
@@ -1467,9 +1467,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
@@ -1477,15 +1477,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
@@ -1494,15 +1494,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
@@ -1511,21 +1511,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
@@ -1571,7 +1571,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "git"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"command",
@@ -2192,7 +2192,7 @@ dependencies = [
[[package]]
name = "komodo_cli"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"clap",
@@ -2208,7 +2208,7 @@ dependencies = [
[[package]]
name = "komodo_client"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2239,7 +2239,7 @@ dependencies = [
[[package]]
name = "komodo_core"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2296,7 +2296,7 @@ dependencies = [
[[package]]
name = "komodo_periphery"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2383,7 +2383,7 @@ dependencies = [
[[package]]
name = "logger"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"komodo_client",
@@ -2447,7 +2447,7 @@ dependencies = [
[[package]]
name = "migrator"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"dotenvy",
@@ -3102,7 +3102,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "periphery_client"
version = "1.15.3"
version = "1.15.4"
dependencies = [
"anyhow",
"komodo_client",
@@ -4250,9 +4250,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.31.4"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be"
checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791"
dependencies = [
"core-foundation-sys",
"libc",
@@ -4880,7 +4880,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "update_logger"
version = "1.15.3"
version = "1.15.4"
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.3"
version = "1.15.4"
edition = "2021"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"
@@ -44,8 +44,8 @@ svi = "1.0.1"
reqwest = { version = "0.12.8", features = ["json"] }
tokio = { version = "1.38.1", features = ["full"] }
tokio-util = "0.7.12"
futures = "0.3.30"
futures-util = "0.3.30"
futures = "0.3.31"
futures-util = "0.3.31"
# SERVER
axum-extra = { version = "0.9.4", features = ["typed-header"] }
@@ -76,7 +76,7 @@ opentelemetry = "0.25.0"
tracing = "0.1.40"
# CONFIG
clap = { version = "4.5.19", features = ["derive"] }
clap = { version = "4.5.20", features = ["derive"] }
dotenvy = "0.15.7"
envy = "0.4.2"
@@ -95,14 +95,14 @@ hex = "0.4.3"
# SYSTEM
bollard = "0.17.1"
sysinfo = "0.31.4"
sysinfo = "0.32.0"
# CLOUD
aws-config = "1.5.7"
aws-sdk-ec2 = "1.75.0"
aws-config = "1.5.8"
aws-sdk-ec2 = "1.77.0"
# MISC
derive_builder = "0.20.1"
derive_builder = "0.20.2"
typeshare = "1.0.3"
octorust = "0.7.0"
dashmap = "6.1.0"

View File

@@ -64,7 +64,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
PermissionLevel::Execute,
)
.await?;
let vars_and_secrets = get_variables_and_secrets().await?;
let mut vars_and_secrets = get_variables_and_secrets().await?;
if build.config.builder_id.is_empty() {
return Err(anyhow!("Must attach builder to RunBuild"));
@@ -85,6 +85,14 @@ impl Resolve<RunBuild, (User, Update)> for State {
update.version = build.config.version;
update_update(update.clone()).await?;
// Add the $VERSION to variables. Use with [[$VERSION]]
if !vars_and_secrets.variables.contains_key("$VERSION") {
vars_and_secrets.variables.insert(
String::from("$VERSION"),
build.config.version.to_string(),
);
}
let git_token = git_token(
&build.config.git_provider,
&build.config.git_account,
@@ -171,7 +179,6 @@ impl Resolve<RunBuild, (User, Update)> for State {
};
// CLONE REPO
let secret_replacers = if !build.config.skip_secret_interp {
// Interpolate variables / secrets into pre build command
let mut global_replacers = HashSet::new();

View File

@@ -88,7 +88,11 @@ const MAX_LOG_LENGTH: u64 = 5000;
impl Resolve<GetDeploymentLog, User> for State {
async fn resolve(
&self,
GetDeploymentLog { deployment, tail }: GetDeploymentLog,
GetDeploymentLog {
deployment,
tail,
timestamps,
}: GetDeploymentLog,
user: User,
) -> anyhow::Result<Log> {
let Deployment {
@@ -109,6 +113,7 @@ impl Resolve<GetDeploymentLog, User> for State {
.request(api::container::GetContainerLog {
name,
tail: cmp::min(tail, MAX_LOG_LENGTH),
timestamps,
})
.await
.context("failed at call to periphery")
@@ -123,6 +128,7 @@ impl Resolve<SearchDeploymentLog, User> for State {
terms,
combinator,
invert,
timestamps,
}: SearchDeploymentLog,
user: User,
) -> anyhow::Result<Log> {
@@ -146,6 +152,7 @@ impl Resolve<SearchDeploymentLog, User> for State {
terms,
combinator,
invert,
timestamps,
})
.await
.context("failed at call to periphery")

View File

@@ -404,6 +404,7 @@ impl Resolve<GetContainerLog, User> for State {
server,
container,
tail,
timestamps,
}: GetContainerLog,
user: User,
) -> anyhow::Result<Log> {
@@ -417,6 +418,7 @@ impl Resolve<GetContainerLog, User> for State {
.request(periphery::container::GetContainerLog {
name: container,
tail: cmp::min(tail, MAX_LOG_LENGTH),
timestamps,
})
.await
.context("failed at call to periphery")
@@ -432,6 +434,7 @@ impl Resolve<SearchContainerLog, User> for State {
terms,
combinator,
invert,
timestamps,
}: SearchContainerLog,
user: User,
) -> anyhow::Result<Log> {
@@ -447,6 +450,7 @@ impl Resolve<SearchContainerLog, User> for State {
terms,
combinator,
invert,
timestamps,
})
.await
.context("failed at call to periphery")

View File

@@ -70,6 +70,7 @@ impl Resolve<GetStackServiceLog, User> for State {
stack,
service,
tail,
timestamps,
}: GetStackServiceLog,
user: User,
) -> anyhow::Result<GetStackServiceLogResponse> {
@@ -85,6 +86,7 @@ impl Resolve<GetStackServiceLog, User> for State {
project: stack.project_name(false),
service,
tail,
timestamps,
})
.await
.context("failed to get stack service log from periphery")
@@ -100,6 +102,7 @@ impl Resolve<SearchStackServiceLog, User> for State {
terms,
combinator,
invert,
timestamps,
}: SearchStackServiceLog,
user: User,
) -> anyhow::Result<SearchStackServiceLogResponse> {
@@ -117,6 +120,7 @@ impl Resolve<SearchStackServiceLog, User> for State {
terms,
combinator,
invert,
timestamps,
})
.await
.context("failed to get stack service log from periphery")

View File

@@ -3,8 +3,9 @@ use std::{str::FromStr, time::Duration};
use anyhow::{anyhow, Context};
use futures::future::join_all;
use komodo_client::{
api::write::CreateServer,
api::write::{CreateBuilder, CreateServer},
entities::{
builder::{PartialBuilderConfig, PartialServerBuilderConfig},
komodo_timestamp,
permission::{Permission, PermissionLevel, UserTarget},
server::{PartialServerConfig, Server},
@@ -280,8 +281,8 @@ async fn startup_open_alert_cleanup() {
}
}
/// Ensures a default server exists with the defined address
pub async fn ensure_first_server() {
/// Ensures a default server / builder exists with the defined address
pub async fn ensure_first_server_and_builder() {
let first_server = &core_config().first_server;
if first_server.is_empty() {
return;
@@ -295,23 +296,49 @@ pub async fn ensure_first_server() {
else {
return;
};
if server.is_some() {
return;
}
let server = if let Some(server) = server {
server
} else {
match State
.resolve(
CreateServer {
name: format!("server-{}", random_string(5)),
config: PartialServerConfig {
address: Some(first_server.to_string()),
enabled: Some(true),
..Default::default()
},
},
system_user().to_owned(),
)
.await
{
Ok(server) => server,
Err(e) => {
error!("Failed to initialize 'first_server'. Failed to CreateServer. {e:?}");
return;
}
}
};
let Ok(None) = db.builders
.find_one(Document::new()).await
.inspect_err(|e| error!("Failed to initialize 'first_builder'. Failed to query db. {e:?}")) else {
return;
};
if let Err(e) = State
.resolve(
CreateServer {
name: format!("server-{}", random_string(5)),
config: PartialServerConfig {
address: Some(first_server.to_string()),
enabled: Some(true),
..Default::default()
},
CreateBuilder {
name: String::from("local"),
config: PartialBuilderConfig::Server(
PartialServerBuilderConfig {
server_id: Some(server.id),
},
),
},
system_user().to_owned(),
)
.await
{
error!("Failed to initialize 'first_server'. Failed to CreateServer. {e:?}");
error!("Failed to initialize 'first_builder'. Failed to CreateBuilder. {e:?}");
}
}

View File

@@ -44,7 +44,7 @@ async fn app() -> anyhow::Result<()> {
);
tokio::join!(
// Maybe initialize first server
helpers::ensure_first_server(),
helpers::ensure_first_server_and_builder(),
// Cleanup open updates / invalid alerts
helpers::startup_cleanup(),
);

View File

@@ -514,8 +514,7 @@ pub async fn create<T: KomodoResource>(
.await
.context("Failed to list all resources for duplicate name check")?
.into_iter()
.find(|r| r.name == name)
.is_some()
.any(|r| r.name == name)
{
return Err(anyhow!("Must provide unique name for resource."));
}

View File

@@ -274,7 +274,7 @@ impl ToToml for Build {
config
.into_iter()
.map(|(key, value)| match key.as_str() {
"builder_id" => return Ok((String::from("builder"), value)),
"builder_id" => Ok((String::from("builder"), value)),
"version" => {
match (
&resource.config.version,

View File

@@ -87,10 +87,18 @@ impl Resolve<build::Build> for State {
// Get command parts
let image_name =
get_image_name(&build).context("failed to make image name")?;
let build_args = parse_build_args(
&environment_vars_from_str(build_args)
.context("Invalid build_args")?,
);
// Add VERSION to build args (if not already there)
let mut build_args = environment_vars_from_str(build_args)
.context("Invalid build_args")?;
if !build_args.iter().any(|a| a.variable == "VERSION") {
build_args.push(EnvironmentVar {
variable: String::from("VERSION"),
value: build.config.version.to_string(),
});
}
let build_args = parse_build_args(&build_args);
let secret_args = environment_vars_from_str(secret_args)
.context("Invalid secret_args")?;
let _secret_args =
@@ -110,13 +118,16 @@ impl Resolve<build::Build> for State {
// Construct command
let command = format!(
"cd {} && docker{buildx} build{build_args}{_secret_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
build_dir.display()
"docker{buildx} build{build_args}{_secret_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
);
if *skip_secret_interp {
let build_log =
run_komodo_command("docker build", command).await;
let build_log = run_komodo_command(
"docker build",
build_dir.as_ref(),
command,
)
.await;
logs.push(build_log);
} else {
// Interpolate any missing secrets
@@ -131,8 +142,12 @@ impl Resolve<build::Build> for State {
)?;
replacers.extend(core_replacers);
let mut build_log =
run_komodo_command("docker build", command).await;
let mut build_log = run_komodo_command(
"docker build",
build_dir.as_ref(),
command,
)
.await;
build_log.command =
svi::replace_in_string(&build_log.command, &replacers);
build_log.stdout =
@@ -229,7 +244,7 @@ impl Resolve<PruneBuilders> for State {
_: (),
) -> anyhow::Result<Log> {
let command = String::from("docker builder prune -a -f");
Ok(run_komodo_command("prune builders", command).await)
Ok(run_komodo_command("prune builders", None, command).await)
}
}
@@ -243,6 +258,6 @@ impl Resolve<PruneBuildx> for State {
_: (),
) -> anyhow::Result<Log> {
let command = String::from("docker buildx prune -a -f");
Ok(run_komodo_command("prune buildx", command).await)
Ok(run_komodo_command("prune buildx", None, command).await)
}
}

View File

@@ -28,6 +28,7 @@ impl Resolve<ListComposeProjects, ()> for State {
let docker_compose = docker_compose();
let res = run_komodo_command(
"list projects",
None,
format!("{docker_compose} ls --all --format json"),
)
.await;
@@ -88,14 +89,17 @@ impl Resolve<GetComposeServiceLog> for State {
project,
service,
tail,
timestamps,
}: GetComposeServiceLog,
_: (),
) -> anyhow::Result<Log> {
let docker_compose = docker_compose();
let timestamps =
timestamps.then_some(" --timestamps").unwrap_or_default();
let command = format!(
"{docker_compose} -p {project} logs {service} --tail {tail}"
"{docker_compose} -p {project} logs {service} --tail {tail}{timestamps}"
);
Ok(run_komodo_command("get stack log", command).await)
Ok(run_komodo_command("get stack log", None, command).await)
}
}
@@ -113,13 +117,16 @@ impl Resolve<GetComposeServiceLogSearch> for State {
terms,
combinator,
invert,
timestamps,
}: GetComposeServiceLogSearch,
_: (),
) -> anyhow::Result<Log> {
let docker_compose = docker_compose();
let grep = log_grep(&terms, combinator, invert);
let command = format!("{docker_compose} -p {project} logs {service} --tail 5000 2>&1 | {grep}");
Ok(run_komodo_command("get stack log grep", command).await)
let timestamps =
timestamps.then_some(" --timestamps").unwrap_or_default();
let command = format!("{docker_compose} -p {project} logs {service} --tail 5000{timestamps} 2>&1 | {grep}");
Ok(run_komodo_command("get stack log grep", None, command).await)
}
}
@@ -289,6 +296,7 @@ impl Resolve<ComposeExecution> for State {
let docker_compose = docker_compose();
let log = run_komodo_command(
"compose command",
None,
format!("{docker_compose} -p {project} {command}"),
)
.await;

View File

@@ -42,11 +42,18 @@ impl Resolve<GetContainerLog> for State {
#[instrument(name = "GetContainerLog", level = "debug", skip(self))]
async fn resolve(
&self,
GetContainerLog { name, tail }: GetContainerLog,
GetContainerLog {
name,
tail,
timestamps,
}: GetContainerLog,
_: (),
) -> anyhow::Result<Log> {
let command = format!("docker logs {name} --tail {tail}");
Ok(run_komodo_command("get container log", command).await)
let timestamps =
timestamps.then_some(" --timestamps").unwrap_or_default();
let command =
format!("docker logs {name} --tail {tail}{timestamps}");
Ok(run_komodo_command("get container log", None, command).await)
}
}
@@ -65,13 +72,20 @@ impl Resolve<GetContainerLogSearch> for State {
terms,
combinator,
invert,
timestamps,
}: GetContainerLogSearch,
_: (),
) -> anyhow::Result<Log> {
let grep = log_grep(&terms, combinator, invert);
let command =
format!("docker logs {name} --tail 5000 2>&1 | {grep}");
Ok(run_komodo_command("get container log grep", command).await)
let timestamps =
timestamps.then_some(" --timestamps").unwrap_or_default();
let command = format!(
"docker logs {name} --tail 5000{timestamps} 2>&1 | {grep}"
);
Ok(
run_komodo_command("get container log grep", None, command)
.await,
)
}
}
@@ -126,6 +140,7 @@ impl Resolve<StartContainer> for State {
Ok(
run_komodo_command(
"docker start",
None,
format!("docker start {name}"),
)
.await,
@@ -145,6 +160,7 @@ impl Resolve<RestartContainer> for State {
Ok(
run_komodo_command(
"docker restart",
None,
format!("docker restart {name}"),
)
.await,
@@ -164,6 +180,7 @@ impl Resolve<PauseContainer> for State {
Ok(
run_komodo_command(
"docker pause",
None,
format!("docker pause {name}"),
)
.await,
@@ -181,6 +198,7 @@ impl Resolve<UnpauseContainer> for State {
Ok(
run_komodo_command(
"docker unpause",
None,
format!("docker unpause {name}"),
)
.await,
@@ -198,10 +216,11 @@ impl Resolve<StopContainer> for State {
_: (),
) -> anyhow::Result<Log> {
let command = stop_container_command(&name, signal, time);
let log = run_komodo_command("docker stop", command).await;
let log = run_komodo_command("docker stop", None, command).await;
if log.stderr.contains("unknown flag: --signal") {
let command = stop_container_command(&name, None, time);
let mut log = run_komodo_command("docker stop", command).await;
let mut log =
run_komodo_command("docker stop", None, command).await;
log.stderr = format!(
"old docker version: unable to use --signal flag{}",
if !log.stderr.is_empty() {
@@ -230,12 +249,14 @@ impl Resolve<RemoveContainer> for State {
let command =
format!("{stop_command} && docker container rm {name}");
let log =
run_komodo_command("docker stop and remove", command).await;
run_komodo_command("docker stop and remove", None, command)
.await;
if log.stderr.contains("unknown flag: --signal") {
let stop_command = stop_container_command(&name, None, time);
let command =
format!("{stop_command} && docker container rm {name}");
let mut log = run_komodo_command("docker stop", command).await;
let mut log =
run_komodo_command("docker stop", None, command).await;
log.stderr = format!(
"old docker version: unable to use --signal flag{}",
if !log.stderr.is_empty() {
@@ -265,7 +286,7 @@ impl Resolve<RenameContainer> for State {
) -> anyhow::Result<Log> {
let new = to_komodo_name(&new_name);
let command = format!("docker rename {curr_name} {new}");
Ok(run_komodo_command("docker rename", command).await)
Ok(run_komodo_command("docker rename", None, command).await)
}
}
@@ -279,7 +300,7 @@ impl Resolve<PruneContainers> for State {
_: (),
) -> anyhow::Result<Log> {
let command = String::from("docker container prune -f");
Ok(run_komodo_command("prune containers", command).await)
Ok(run_komodo_command("prune containers", None, command).await)
}
}
@@ -297,14 +318,12 @@ impl Resolve<StartAllContainers> for State {
.await
.context("failed to list all containers on host")?;
let futures =
containers.iter().map(
|ContainerListItem { name, .. }| {
let command = format!("docker start {name}");
async move {
run_komodo_command(&command.clone(), command).await
}
},
);
containers.iter().map(|ContainerListItem { name, .. }| {
let command = format!("docker start {name}");
async move {
run_komodo_command(&command.clone(), None, command).await
}
});
Ok(join_all(futures).await)
}
}
@@ -322,14 +341,13 @@ impl Resolve<RestartAllContainers> for State {
.list_containers()
.await
.context("failed to list all containers on host")?;
let futures = containers.iter().map(
|ContainerListItem { name, .. }| {
let futures =
containers.iter().map(|ContainerListItem { name, .. }| {
let command = format!("docker restart {name}");
async move {
run_komodo_command(&command.clone(), command).await
run_komodo_command(&command.clone(), None, command).await
}
},
);
});
Ok(join_all(futures).await)
}
}
@@ -347,14 +365,13 @@ impl Resolve<PauseAllContainers> for State {
.list_containers()
.await
.context("failed to list all containers on host")?;
let futures = containers.iter().map(
|ContainerListItem { name, .. }| {
let futures =
containers.iter().map(|ContainerListItem { name, .. }| {
let command = format!("docker pause {name}");
async move {
run_komodo_command(&command.clone(), command).await
run_komodo_command(&command.clone(), None, command).await
}
},
);
});
Ok(join_all(futures).await)
}
}
@@ -372,14 +389,13 @@ impl Resolve<UnpauseAllContainers> for State {
.list_containers()
.await
.context("failed to list all containers on host")?;
let futures = containers.iter().map(
|ContainerListItem { name, .. }| {
let futures =
containers.iter().map(|ContainerListItem { name, .. }| {
let command = format!("docker unpause {name}");
async move {
run_komodo_command(&command.clone(), command).await
run_komodo_command(&command.clone(), None, command).await
}
},
);
});
Ok(join_all(futures).await)
}
}
@@ -401,6 +417,7 @@ impl Resolve<StopAllContainers> for State {
|ContainerListItem { name, .. }| async move {
run_komodo_command(
&format!("docker stop {name}"),
None,
stop_container_command(name, None, None),
)
.await

View File

@@ -87,7 +87,7 @@ impl Resolve<Deploy> for State {
debug!("docker run command: {command}");
if deployment.config.skip_secret_interp {
Ok(run_komodo_command("docker run", command).await)
Ok(run_komodo_command("docker run", None, command).await)
} else {
let command = svi::interpolate_variables(
&command,
@@ -107,7 +107,8 @@ impl Resolve<Deploy> for State {
};
replacers.extend(core_replacers);
let mut log = run_komodo_command("docker run", command).await;
let mut log =
run_komodo_command("docker run", None, command).await;
log.command = svi::replace_in_string(&log.command, &replacers);
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
log.stderr = svi::replace_in_string(&log.stderr, &replacers);

View File

@@ -44,7 +44,7 @@ impl Resolve<DeleteImage> for State {
_: (),
) -> anyhow::Result<Log> {
let command = format!("docker image rm {name}");
Ok(run_komodo_command("delete image", command).await)
Ok(run_komodo_command("delete image", None, command).await)
}
}
@@ -58,6 +58,6 @@ impl Resolve<PruneImages> for State {
_: (),
) -> anyhow::Result<Log> {
let command = String::from("docker image prune -a -f");
Ok(run_komodo_command("prune images", command).await)
Ok(run_komodo_command("prune images", None, command).await)
}
}

View File

@@ -255,7 +255,7 @@ impl Resolve<RunCommand> for State {
} else {
format!("cd {path} && {command}")
};
run_komodo_command("run command", command).await
run_komodo_command("run command", None, command).await
})
.await
.context("failure in spawned task")
@@ -270,6 +270,6 @@ impl Resolve<PruneSystem> for State {
_: (),
) -> anyhow::Result<Log> {
let command = String::from("docker system prune -a -f --volumes");
Ok(run_komodo_command("prune system", command).await)
Ok(run_komodo_command("prune system", None, command).await)
}
}

View File

@@ -34,7 +34,7 @@ impl Resolve<CreateNetwork> for State {
None => String::new(),
};
let command = format!("docker network create{driver} {name}");
Ok(run_komodo_command("create network", command).await)
Ok(run_komodo_command("create network", None, command).await)
}
}
@@ -48,7 +48,7 @@ impl Resolve<DeleteNetwork> for State {
_: (),
) -> anyhow::Result<Log> {
let command = format!("docker network rm {name}");
Ok(run_komodo_command("delete network", command).await)
Ok(run_komodo_command("delete network", None, command).await)
}
}
@@ -62,6 +62,6 @@ impl Resolve<PruneNetworks> for State {
_: (),
) -> anyhow::Result<Log> {
let command = String::from("docker network prune -f");
Ok(run_komodo_command("prune networks", command).await)
Ok(run_komodo_command("prune networks", None, command).await)
}
}

View File

@@ -28,7 +28,7 @@ impl Resolve<DeleteVolume> for State {
_: (),
) -> anyhow::Result<Log> {
let command = format!("docker volume rm {name}");
Ok(run_komodo_command("delete volume", command).await)
Ok(run_komodo_command("delete volume", None, command).await)
}
}
@@ -42,6 +42,6 @@ impl Resolve<PruneVolumes> for State {
_: (),
) -> anyhow::Result<Log> {
let command = String::from("docker volume prune -a -f");
Ok(run_komodo_command("prune volumes", command).await)
Ok(run_komodo_command("prune volumes", None, command).await)
}
}

View File

@@ -16,8 +16,10 @@ use resolver_api::Resolve;
use tokio::fs;
use crate::{
config::periphery_config, docker::docker_login,
helpers::parse_extra_args, State,
config::periphery_config,
docker::docker_login,
helpers::{interpolate_variables, parse_extra_args},
State,
};
pub fn docker_compose() -> &'static str {
@@ -101,7 +103,6 @@ pub async fn compose_up(
}
let docker_compose = docker_compose();
let run_dir = run_directory.display();
let service_arg = service
.as_ref()
.map(|service| format!(" {service}"))
@@ -146,10 +147,15 @@ pub async fn compose_up(
let build_extra_args =
parse_extra_args(&stack.config.build_extra_args);
let command = format!(
"cd {run_dir} && {docker_compose} -p {project_name} -f {file_args}{env_file} build{build_extra_args}{service_arg}",
"{docker_compose} -p {project_name} -f {file_args}{env_file} build{build_extra_args}{service_arg}",
);
if stack.config.skip_secret_interp {
let log = run_komodo_command("compose build", command).await;
let log = run_komodo_command(
"compose build",
run_directory.as_ref(),
command,
)
.await;
res.logs.push(log);
} else {
let (command, mut replacers) = svi::interpolate_variables(
@@ -160,8 +166,12 @@ pub async fn compose_up(
).context("failed to interpolate periphery secrets into stack build command")?;
replacers.extend(core_replacers.clone());
let mut log =
run_komodo_command("compose build", command).await;
let mut log = run_komodo_command(
"compose build",
run_directory.as_ref(),
command,
)
.await;
log.command = svi::replace_in_string(&log.command, &replacers);
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
@@ -177,13 +187,15 @@ pub async fn compose_up(
}
}
//
if stack.config.auto_pull {
// Pull images before destroying to minimize downtime.
// If this fails, do not continue.
let log = run_komodo_command(
"compose pull",
run_directory.as_ref(),
format!(
"cd {run_dir} && {docker_compose} -p {project_name} -f {file_args}{env_file} pull{service_arg}",
"{docker_compose} -p {project_name} -f {file_args}{env_file} pull{service_arg}",
),
)
.await;
@@ -201,19 +213,16 @@ pub async fn compose_up(
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",
)?;
let (full_command, mut replacers) =
interpolate_variables(&stack.config.pre_deploy.command)
.context(
"failed to interpolate secrets into pre_deploy command",
)?;
replacers.extend(core_replacers.to_owned());
let mut pre_deploy_log = run_komodo_command(
"pre deploy",
format!("cd {} && {full_command}", pre_deploy_path.display()),
pre_deploy_path.as_ref(),
&full_command,
)
.await;
@@ -234,11 +243,8 @@ pub async fn compose_up(
} else {
let pre_deploy_log = run_komodo_command(
"pre deploy",
format!(
"cd {} && {}",
pre_deploy_path.display(),
stack.config.pre_deploy.command
),
pre_deploy_path.as_ref(),
&stack.config.pre_deploy.command,
)
.await;
tracing::debug!(
@@ -255,20 +261,26 @@ pub async fn compose_up(
}
}
// Take down the existing containers.
// This one tries to use the previously deployed service name, to ensure the right stack is taken down.
compose_down(&last_project_name, service, res)
.await
.context("failed to destroy existing containers")?;
if stack.config.destroy_before_deploy
// Also check if project name changed, which also requires taking down.
|| last_project_name != project_name
{
// 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)
.await
.context("failed to destroy existing containers")?;
}
// Run compose up
let extra_args = parse_extra_args(&stack.config.extra_args);
let command = format!(
"cd {run_dir} && {docker_compose} -p {project_name} -f {file_args}{env_file} up -d{extra_args}{service_arg}",
"{docker_compose} -p {project_name} -f {file_args}{env_file} up -d{extra_args}{service_arg}",
);
let log = if stack.config.skip_secret_interp {
run_komodo_command("compose up", command).await
run_komodo_command("compose up", run_directory.as_ref(), command)
.await
} else {
let (command, mut replacers) = svi::interpolate_variables(
&command,
@@ -278,7 +290,12 @@ pub async fn compose_up(
).context("failed to interpolate periphery secrets into stack run command")?;
replacers.extend(core_replacers);
let mut log = run_komodo_command("compose up", command).await;
let mut log = run_komodo_command(
"compose up",
run_directory.as_ref(),
command,
)
.await;
log.command = svi::replace_in_string(&log.command, &replacers);
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
@@ -323,7 +340,7 @@ async fn write_stack<'a>(
.config
.skip_secret_interp
.then_some(&periphery_config().secrets),
&run_directory,
run_directory.as_ref(),
&mut res.logs,
)
.await
@@ -361,7 +378,7 @@ async fn write_stack<'a>(
.config
.skip_secret_interp
.then_some(&periphery_config().secrets),
&run_directory,
run_directory.as_ref(),
&mut res.logs,
)
.await
@@ -517,6 +534,7 @@ async fn compose_down(
.unwrap_or_default();
let log = run_komodo_command(
"compose down",
None,
format!("{docker_compose} -p {project} down{service_arg}"),
)
.await;

View File

@@ -935,7 +935,7 @@ pub async fn docker_login(
#[instrument]
pub async fn pull_image(image: &str) -> Log {
let command = format!("docker pull {image}");
run_komodo_command("docker pull", command).await
run_komodo_command("docker pull", None, command).await
}
pub fn stop_container_command(

View File

@@ -66,3 +66,14 @@ pub fn log_grep(
}
}
}
pub fn interpolate_variables(
input: &str,
) -> svi::Result<(String, Vec<(String, String)>)> {
svi::interpolate_variables(
input,
&periphery_config().secrets,
svi::Interpolator::DoubleBrackets,
true,
)
}

View File

@@ -128,7 +128,7 @@ pub struct UnpauseStack {
//
/// Starts the target stack. `docker compose stop`. Response: [Update]
/// Stops the target stack. `docker compose stop`. Response: [Update]
#[typeshare]
#[derive(
Debug,

View File

@@ -120,6 +120,9 @@ pub struct GetDeploymentLog {
/// Max: 5000.
#[serde(default = "default_tail")]
pub tail: U64,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
fn default_tail() -> u64 {
@@ -156,6 +159,9 @@ pub struct SearchDeploymentLog {
/// Invert the results, ie return all lines that DON'T match the terms / combinator.
#[serde(default)]
pub invert: bool,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
#[typeshare]

View File

@@ -303,6 +303,9 @@ pub struct GetContainerLog {
/// Max: 5000.
#[serde(default = "default_tail")]
pub tail: U64,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
fn default_tail() -> u64 {
@@ -341,6 +344,9 @@ pub struct SearchContainerLog {
/// Invert the results, ie return all lines that DON'T match the terms / combinator.
#[serde(default)]
pub invert: bool,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
#[typeshare]

View File

@@ -69,6 +69,9 @@ pub struct GetStackServiceLog {
/// Max: 5000.
#[serde(default = "default_tail")]
pub tail: U64,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
fn default_tail() -> u64 {
@@ -107,6 +110,9 @@ pub struct SearchStackServiceLog {
/// Invert the results, ie return all lines that DON'T match the terms / combinator.
#[serde(default)]
pub invert: bool,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
#[typeshare]

View File

@@ -219,7 +219,7 @@ impl SystemCommand {
}
pub fn is_none(&self) -> bool {
self.path.is_empty() || self.command.is_empty()
self.command.is_empty()
}
}
@@ -614,7 +614,7 @@ impl CloneArgs {
pub fn path(&self, repo_dir: &Path) -> PathBuf {
let path = match &self.destination {
Some(destination) => PathBuf::from(&destination),
None => repo_dir.join(&to_komodo_name(&self.name)),
None => repo_dir.join(to_komodo_name(&self.name)),
};
path.components().collect::<PathBuf>()
}
@@ -650,9 +650,7 @@ impl CloneArgs {
.join(self.provider.replace('/', "-"))
.join(repo.replace('/', "-"))
.join(self.branch.replace('/', "-"))
.join(
self.commit.as_ref().map(String::as_str).unwrap_or("latest"),
);
.join(self.commit.as_deref().unwrap_or("latest"));
Ok(res)
}
}

View File

@@ -104,7 +104,7 @@ pub struct ProcedureStage {
#[serde(default = "default_enabled")]
pub enabled: bool,
/// The executions in the stage
#[serde(default)]
#[serde(default, alias = "execution")]
pub executions: Vec<EnabledExecution>,
}

View File

@@ -213,6 +213,11 @@ pub struct StackConfig {
#[builder(default)]
pub run_build: bool,
/// Whether to run `docker compose down` before `compose up`.
#[serde(default)]
#[builder(default)]
pub destroy_before_deploy: bool,
/// Whether to skip secret interpolation into the stack environment variables.
#[serde(default)]
#[builder(default)]
@@ -426,6 +431,7 @@ impl Default for StackConfig {
environment: Default::default(),
env_file_path: default_env_file_path(),
run_build: Default::default(),
destroy_before_deploy: Default::default(),
build_extra_args: Default::default(),
skip_secret_interp: Default::default(),
git_provider: default_git_provider(),

View File

@@ -2323,6 +2323,8 @@ export interface StackConfig {
* Combine with build_extra_args for custom behaviors.
*/
run_build?: boolean;
/** Whether to run `docker compose down` before `compose up`. */
destroy_before_deploy?: boolean;
/** Whether to skip secret interpolation into the stack environment variables. */
skip_secret_interp?: boolean;
/**
@@ -3797,7 +3799,7 @@ export interface UnpauseStack {
service?: string;
}
/** Starts the target stack. `docker compose stop`. Response: [Update] */
/** Stops the target stack. `docker compose stop`. Response: [Update] */
export interface StopStack {
/** Id or name */
stack: string;
@@ -4112,6 +4114,8 @@ export interface GetDeploymentLog {
* Max: 5000.
*/
tail: U64;
/** Enable `--timestamps` */
timestamps?: boolean;
}
export enum SearchCombinator {
@@ -4139,6 +4143,8 @@ export interface SearchDeploymentLog {
combinator?: SearchCombinator;
/** Invert the results, ie return all lines that DON'T match the terms / combinator. */
invert?: boolean;
/** Enable `--timestamps` */
timestamps?: boolean;
}
/**
@@ -4605,6 +4611,8 @@ export interface GetContainerLog {
* Max: 5000.
*/
tail: U64;
/** Enable `--timestamps` */
timestamps?: boolean;
}
/**
@@ -4629,6 +4637,8 @@ export interface SearchContainerLog {
combinator?: SearchCombinator;
/** Invert the results, ie return all lines that DON'T match the terms / combinator. */
invert?: boolean;
/** Enable `--timestamps` */
timestamps?: boolean;
}
/** Inspect a docker container on the server. Response: [Container]. */
@@ -4819,6 +4829,8 @@ export interface GetStackServiceLog {
* Max: 5000.
*/
tail: U64;
/** Enable `--timestamps` */
timestamps?: boolean;
}
/**
@@ -4843,6 +4855,8 @@ export interface SearchStackServiceLog {
combinator?: SearchCombinator;
/** Invert the results, ie return all lines that DON'T match the terms / combinator. */
invert?: boolean;
/** Enable `--timestamps` */
timestamps?: boolean;
}
/**

View File

@@ -44,9 +44,12 @@ pub struct GetComposeServiceLog {
pub project: String,
/// The service name
pub service: String,
/// pass `--tail` for only recent log contents
/// Pass `--tail` for only recent log contents. Max of 5000
#[serde(default = "default_tail")]
pub tail: u64,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
fn default_tail() -> u64 {
@@ -72,6 +75,9 @@ pub struct GetComposeServiceLogSearch {
/// Invert the search (search for everything not matching terms)
#[serde(default)]
pub invert: bool,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
//

View File

@@ -23,6 +23,9 @@ pub struct GetContainerLog {
pub name: String,
#[serde(default = "default_tail")]
pub tail: u64,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
fn default_tail() -> u64 {
@@ -40,6 +43,9 @@ pub struct GetContainerLogSearch {
pub combinator: SearchCombinator,
#[serde(default)]
pub invert: bool,
/// Enable `--timestamps`
#[serde(default)]
pub timestamps: bool,
}
//

View File

@@ -749,7 +749,7 @@ export const SystemCommand = ({
/>
</div>
<MonacoEditor
value={value?.command}
value={value?.command || " # Add multiple commands on new lines. Supports comments.\n "}
language="shell"
onValueChange={(command) => set({ ...(value || {}), command })}
readOnly={disabled}

View File

@@ -100,7 +100,7 @@ export const BuildConfig = ({
<ConfigInput
className="text-lg w-[200px]"
label="Version"
description="Version the image tag using server (major.minor.patch)"
description="Version the image with major.minor.patch. It can be interpolated using [[$VERSION]]."
placeholder="0.0.0"
value={version}
onChange={(version) => set({ version: version as any })}

View File

@@ -43,6 +43,10 @@ const DeploymentLogsInner = ({
const [invert, setInvert] = useState(false);
const [search, setSearch] = useState("");
const [poll, setPoll] = useLocalStorage("log-poll-v1", false);
const [timestamps, setTimestamps] = useLocalStorage(
"log-timestamps-v1",
false
);
const addTerm = () => {
if (!search.length) return;
@@ -61,8 +65,8 @@ const DeploymentLogsInner = ({
};
const { Log, refetch, stderr } = terms.length
? SearchLogs(id, terms, invert)
: NoSearchLogs(id, tail, stream);
? SearchLogs(id, terms, invert, timestamps)
: NoSearchLogs(id, tail, timestamps, stream);
useEffect(() => {
const interval = setInterval(() => {
@@ -77,9 +81,8 @@ const DeploymentLogsInner = ({
actions={
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
<div className="text-muted-foreground flex gap-1">
<div>Invert</div>
<div className="hidden xl:block">Search</div>
<div className="text-muted-foreground flex gap-1 text-sm">
Invert
</div>
<Switch checked={invert} onCheckedChange={setInvert} />
</div>
@@ -126,9 +129,19 @@ const DeploymentLogsInner = ({
<Button variant="secondary" size="icon" onClick={() => refetch()}>
<RefreshCw className="w-4 h-4" />
</Button>
<div className="flex items-center gap-2">
<div className="text-muted-foreground">Poll</div>
<Switch checked={poll} onCheckedChange={setPoll} />
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setTimestamps((t) => !t)}
>
<div className="text-muted-foreground text-sm">Timestamps</div>
<Switch checked={timestamps} />
</div>
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setPoll((p) => !p)}
>
<div className="text-muted-foreground text-sm">Poll</div>
<Switch checked={poll} />
</div>
<TailLengthSelector
selected={tail}
@@ -143,12 +156,17 @@ const DeploymentLogsInner = ({
);
};
const NoSearchLogs = (id: string, tail: string, stream: string) => {
const { data: log, refetch } = useRead(
"GetDeploymentLog",
{ deployment: id, tail: Number(tail) },
{ refetchInterval: 30000 }
);
const NoSearchLogs = (
id: string,
tail: string,
timestamps: boolean,
stream: string
) => {
const { data: log, refetch } = useRead("GetDeploymentLog", {
deployment: id,
tail: Number(tail),
timestamps,
});
return {
Log: (
<div className="relative">
@@ -160,12 +178,18 @@ const NoSearchLogs = (id: string, tail: string, stream: string) => {
};
};
const SearchLogs = (id: string, terms: string[], invert: boolean) => {
const SearchLogs = (
id: string,
terms: string[],
invert: boolean,
timestamps: boolean
) => {
const { data: log, refetch } = useRead("SearchDeploymentLog", {
deployment: id,
terms,
combinator: Types.SearchCombinator.And,
invert,
timestamps,
});
return {
Log: (

View File

@@ -389,6 +389,17 @@ export const StackConfig = ({
),
},
},
{
label: "Destroy",
labelHidden: true,
components: {
run_build: {
label: "Destroy Before Deploy",
description:
"Ensure 'docker compose down' is run before redeploying the Stack.",
},
},
},
];
if (mode === undefined) {

View File

@@ -25,6 +25,10 @@ export const ContainerLogs = ({
const [invert, setInvert] = useState(false);
const [search, setSearch] = useState("");
const [poll, setPoll] = useLocalStorage("log-poll-v1", false);
const [timestamps, setTimestamps] = useLocalStorage(
"log-timestamps-v1",
false
);
const addTerm = () => {
if (!search.length) return;
@@ -43,8 +47,8 @@ export const ContainerLogs = ({
};
const { Log, refetch, stderr } = terms.length
? SearchLogs(id, container_name, terms, invert)
: NoSearchLogs(id, container_name, tail, stream);
? SearchLogs(id, container_name, terms, invert, timestamps)
: NoSearchLogs(id, container_name, tail, timestamps, stream);
useEffect(() => {
const interval = setInterval(() => {
@@ -61,10 +65,7 @@ export const ContainerLogs = ({
actions={
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
<div className="text-muted-foreground flex gap-1">
<div>Invert</div>
<div className="hidden xl:block">Search</div>
</div>
<div className="text-muted-foreground flex gap-1">Invert</div>
<Switch checked={invert} onCheckedChange={setInvert} />
</div>
{terms.map((term, index) => (
@@ -110,9 +111,19 @@ export const ContainerLogs = ({
<Button variant="secondary" size="icon" onClick={() => refetch()}>
<RefreshCw className="w-4 h-4" />
</Button>
<div className="flex items-center gap-2">
<div className="text-muted-foreground">Poll</div>
<Switch checked={poll} onCheckedChange={setPoll} />
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setTimestamps((t) => !t)}
>
<div className="text-muted-foreground text-sm">Timestamps</div>
<Switch checked={timestamps} />
</div>
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setPoll((p) => !p)}
>
<div className="text-muted-foreground text-sm">Poll</div>
<Switch checked={poll} />
</div>
<TailLengthSelector
selected={tail}
@@ -131,13 +142,15 @@ const NoSearchLogs = (
id: string,
container: string,
tail: string,
timestamps: boolean,
stream: string
) => {
const { data: log, refetch } = useRead(
"GetContainerLog",
{ server: id, container, tail: Number(tail) },
{ refetchInterval: 30000 }
);
const { data: log, refetch } = useRead("GetContainerLog", {
server: id,
container,
tail: Number(tail),
timestamps,
});
return {
Log: (
<div className="relative">
@@ -153,7 +166,8 @@ const SearchLogs = (
id: string,
container: string,
terms: string[],
invert: boolean
invert: boolean,
timestamps: boolean
) => {
const { data: log, refetch } = useRead("SearchContainerLog", {
server: id,
@@ -161,6 +175,7 @@ const SearchLogs = (
terms,
combinator: Types.SearchCombinator.And,
invert,
timestamps,
});
return {
Log: (

View File

@@ -45,6 +45,10 @@ const StackLogsInner = ({
const [invert, setInvert] = useState(false);
const [search, setSearch] = useState("");
const [poll, setPoll] = useLocalStorage("log-poll-v1", false);
const [timestamps, setTimestamps] = useLocalStorage(
"log-timestamps-v1",
false
);
const addTerm = () => {
if (!search.length) return;
@@ -63,8 +67,8 @@ const StackLogsInner = ({
};
const { Log, refetch, stderr } = terms.length
? SearchLogs(id, service, terms, invert)
: NoSearchLogs(id, service, tail, stream);
? SearchLogs(id, service, terms, invert, timestamps)
: NoSearchLogs(id, service, tail, timestamps, stream);
useEffect(() => {
const interval = setInterval(() => {
@@ -81,9 +85,8 @@ const StackLogsInner = ({
actions={
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
<div className="text-muted-foreground flex gap-1">
<div>Invert</div>
<div className="hidden xl:block">Search</div>
<div className="text-muted-foreground flex gap-1 text-sm">
Invert
</div>
<Switch checked={invert} onCheckedChange={setInvert} />
</div>
@@ -130,9 +133,19 @@ const StackLogsInner = ({
<Button variant="secondary" size="icon" onClick={() => refetch()}>
<RefreshCw className="w-4 h-4" />
</Button>
<div className="flex items-center gap-2">
<div className="text-muted-foreground">Poll</div>
<Switch checked={poll} onCheckedChange={setPoll} />
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setTimestamps((t) => !t)}
>
<div className="text-muted-foreground text-sm">Timestamps</div>
<Switch checked={timestamps} />
</div>
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => setPoll((p) => !p)}
>
<div className="text-muted-foreground text-sm">Poll</div>
<Switch checked={poll} />
</div>
<TailLengthSelector
selected={tail}
@@ -151,13 +164,15 @@ const NoSearchLogs = (
id: string,
service: string,
tail: string,
timestamps: boolean,
stream: string
) => {
const { data: log, refetch } = useRead(
"GetStackServiceLog",
{ stack: id, service, tail: Number(tail) },
{ refetchInterval: 30000 }
);
const { data: log, refetch } = useRead("GetStackServiceLog", {
stack: id,
service,
tail: Number(tail),
timestamps,
});
return {
Log: (
<div className="relative">
@@ -173,7 +188,8 @@ const SearchLogs = (
id: string,
service: string,
terms: string[],
invert: boolean
invert: boolean,
timestamps: boolean
) => {
const { data: log, refetch } = useRead("SearchStackServiceLog", {
stack: id,
@@ -181,6 +197,7 @@ const SearchLogs = (
terms,
combinator: Types.SearchCombinator.And,
invert,
timestamps,
});
return {
Log: (

View File

@@ -1,12 +1,57 @@
use std::path::Path;
use komodo_client::entities::{komodo_timestamp, update::Log};
use run_command::{async_run_command, CommandOutput};
pub async fn run_komodo_command(stage: &str, command: String) -> Log {
/// Parses commands out of multiline string
/// and chains them together with '&&'
///
/// Supports full line and end of line comments. See [parse_multiline_command].
pub async fn run_komodo_command(
stage: &str,
path: impl Into<Option<&Path>>,
command: impl AsRef<str>,
) -> Log {
let command = parse_multiline_command(command);
let command = if let Some(path) = path.into() {
format!("cd {} && {command}", path.display(),)
} else {
command
};
let start_ts = komodo_timestamp();
let output = async_run_command(&command).await;
output_into_log(stage, command, start_ts, output)
}
/// Parses commands out of multiline string
/// and chains them together with '&&'
///
/// Supports full line and end of line comments.
///
/// ## Example:
/// ```sh
/// # comments supported
/// sh ./shell1.sh # end of line supported
/// sh ./shell2.sh
/// # print done
/// echo done
/// ```
/// becomes
/// ```sh
/// sh ./shell1.sh && sh ./shell2.sh && echo done
/// ```
pub fn parse_multiline_command(command: impl AsRef<str>) -> String {
command
.as_ref()
.split('\n')
.map(str::trim)
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.filter_map(|line| line.split(" #").next())
.map(str::trim)
.collect::<Vec<_>>()
.join(" && ")
}
pub fn output_into_log(
stage: &str,
command: String,

View File

@@ -39,8 +39,7 @@ where
{
let args: CloneArgs = clone_args.into();
let repo_dir = args.path(repo_dir);
let repo_url =
args.remote_url(access_token.as_ref().map(String::as_str))?;
let repo_url = args.remote_url(access_token.as_deref())?;
let mut logs = clone_inner(
&repo_url,
@@ -116,7 +115,8 @@ where
replacers.extend(core_replacers.to_owned());
let mut on_clone_log = run_komodo_command(
"on clone",
format!("cd {} && {full_command}", on_clone_path.display()),
on_clone_path.as_ref(),
full_command,
)
.await;
@@ -137,11 +137,8 @@ where
} else {
let on_clone_log = run_komodo_command(
"on clone",
format!(
"cd {} && {}",
on_clone_path.display(),
command.command
),
on_clone_path.as_ref(),
&command.command,
)
.await;
tracing::debug!(
@@ -170,7 +167,8 @@ where
replacers.extend(core_replacers.to_owned());
let mut on_pull_log = run_komodo_command(
"on pull",
format!("cd {} && {full_command}", on_pull_path.display()),
on_pull_path.as_ref(),
&full_command,
)
.await;
@@ -191,11 +189,8 @@ where
} else {
let on_pull_log = run_komodo_command(
"on pull",
format!(
"cd {} && {}",
on_pull_path.display(),
command.command
),
on_pull_path.as_ref(),
&command.command,
)
.await;
tracing::debug!(
@@ -256,10 +251,8 @@ async fn clone_inner(
if let Some(commit) = commit {
let reset_log = run_komodo_command(
"set commit",
format!(
"cd {} && git reset --hard {commit}",
destination.display()
),
destination,
format!("git reset --hard {commit}",),
)
.await;
logs.push(reset_log);

View File

@@ -38,16 +38,13 @@ where
{
let args: CloneArgs = clone_args.into();
let path = args.path(repo_dir);
let path_display = path.display();
let repo_url =
args.remote_url(access_token.as_ref().map(String::as_str))?;
let repo_url = args.remote_url(access_token.as_deref())?;
// Set remote url
let mut set_remote = run_komodo_command(
"set git remote",
format!(
"cd {path_display} && git remote set-url origin {repo_url}"
),
path.as_ref(),
format!("git remote set-url origin {repo_url}"),
)
.await;
@@ -70,7 +67,8 @@ where
let checkout = run_komodo_command(
"checkout branch",
format!("cd {path_display} && git checkout -f {}", args.branch),
path.as_ref(),
format!("git checkout -f {}", args.branch),
)
.await;
@@ -83,12 +81,12 @@ where
});
}
let command = format!(
"cd {path_display} && git pull --rebase --force origin {}",
args.branch
);
let pull_log = run_komodo_command("git pull", command).await;
let pull_log = run_komodo_command(
"git pull",
path.as_ref(),
format!("git pull --rebase --force origin {}", args.branch),
)
.await;
let mut logs = vec![pull_log];
@@ -104,7 +102,8 @@ where
if let Some(commit) = args.commit {
let reset_log = run_komodo_command(
"set commit",
format!("cd {path_display} && git reset --hard {commit}"),
path.as_ref(),
format!("git reset --hard {commit}"),
)
.await;
logs.push(reset_log);
@@ -144,7 +143,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 = path.join(&command.path);
if let Some(secrets) = secrets {
let (full_command, mut replacers) =
@@ -174,7 +173,8 @@ where
replacers.extend(core_replacers.to_owned());
let mut on_pull_log = run_komodo_command(
"on pull",
format!("cd {} && {full_command}", on_pull_path.display()),
on_pull_path.as_ref(),
&full_command,
)
.await;
@@ -195,11 +195,8 @@ where
} else {
let on_pull_log = run_komodo_command(
"on pull",
format!(
"cd {} && {}",
on_pull_path.display(),
command.command
),
on_pull_path.as_ref(),
&command.command,
)
.await;
tracing::debug!(