mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-11 00:01:21 +00:00
251 lines
6.6 KiB
Rust
251 lines
6.6 KiB
Rust
use anyhow::{anyhow, Context};
|
|
use command::{
|
|
run_komodo_command, run_komodo_command_with_interpolation,
|
|
};
|
|
use formatting::format_serror;
|
|
use komodo_client::{
|
|
entities::{
|
|
build::{Build, BuildConfig},
|
|
environment_vars_from_str, get_image_name, optional_string,
|
|
to_komodo_name,
|
|
update::Log,
|
|
EnvironmentVar, Version,
|
|
},
|
|
parsers::QUOTE_PATTERN,
|
|
};
|
|
use periphery_client::api::build::{
|
|
self, PruneBuilders, PruneBuildx,
|
|
};
|
|
use resolver_api::Resolve;
|
|
|
|
use crate::{
|
|
config::periphery_config,
|
|
docker::docker_login,
|
|
helpers::{parse_extra_args, parse_labels},
|
|
};
|
|
|
|
impl Resolve<super::Args> for build::Build {
|
|
#[instrument(name = "Build", skip_all, fields(build = self.build.name.to_string()))]
|
|
async fn resolve(
|
|
self,
|
|
_: &super::Args,
|
|
) -> serror::Result<Vec<Log>> {
|
|
let build::Build {
|
|
build,
|
|
registry_token,
|
|
additional_tags,
|
|
replacers: core_replacers,
|
|
} = self;
|
|
let Build {
|
|
name,
|
|
config:
|
|
BuildConfig {
|
|
version,
|
|
image_tag,
|
|
skip_secret_interp,
|
|
build_path,
|
|
dockerfile_path,
|
|
build_args,
|
|
secret_args,
|
|
labels,
|
|
extra_args,
|
|
use_buildx,
|
|
image_registry,
|
|
..
|
|
},
|
|
..
|
|
} = &build;
|
|
|
|
let mut logs = Vec::new();
|
|
|
|
// Maybe docker login
|
|
let should_push = match docker_login(
|
|
&image_registry.domain,
|
|
&image_registry.account,
|
|
registry_token.as_deref(),
|
|
)
|
|
.await
|
|
{
|
|
Ok(should_push) => should_push,
|
|
Err(e) => {
|
|
logs.push(Log::error(
|
|
"Docker Login",
|
|
format_serror(
|
|
&e.context("failed to login to docker registry").into(),
|
|
),
|
|
));
|
|
return Ok(logs);
|
|
}
|
|
};
|
|
|
|
let name = to_komodo_name(name);
|
|
|
|
// Get paths
|
|
let build_dir =
|
|
periphery_config().repo_dir.join(&name).join(build_path);
|
|
let dockerfile_path = match optional_string(dockerfile_path) {
|
|
Some(dockerfile_path) => dockerfile_path.to_owned(),
|
|
None => "Dockerfile".to_owned(),
|
|
};
|
|
|
|
// Get command parts
|
|
let image_name =
|
|
get_image_name(&build).context("failed to make image name")?;
|
|
|
|
// 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 command_secret_args =
|
|
parse_secret_args(&secret_args, *skip_secret_interp)?;
|
|
|
|
let labels = parse_labels(
|
|
&environment_vars_from_str(labels).context("Invalid labels")?,
|
|
);
|
|
let extra_args = parse_extra_args(extra_args);
|
|
let buildx = if *use_buildx { " buildx" } else { "" };
|
|
let image_tags =
|
|
image_tags(&image_name, image_tag, version, &additional_tags);
|
|
let maybe_push = if should_push { " --push" } else { "" };
|
|
|
|
// Construct command
|
|
let command = format!(
|
|
"docker{buildx} build{build_args}{command_secret_args}{extra_args}{labels}{image_tags}{maybe_push} -f {dockerfile_path} .",
|
|
);
|
|
|
|
if *skip_secret_interp {
|
|
let build_log = run_komodo_command(
|
|
"Docker Build",
|
|
build_dir.as_ref(),
|
|
command,
|
|
)
|
|
.await;
|
|
logs.push(build_log);
|
|
} else {
|
|
run_komodo_command_with_interpolation(
|
|
"Docker Build",
|
|
build_dir.as_ref(),
|
|
command,
|
|
false,
|
|
&periphery_config().secrets,
|
|
&core_replacers,
|
|
)
|
|
.await
|
|
.map(|log| logs.push(log));
|
|
}
|
|
|
|
cleanup_secret_env_vars(&secret_args);
|
|
|
|
Ok(logs)
|
|
}
|
|
}
|
|
|
|
fn image_tags(
|
|
image_name: &str,
|
|
custom_tag: &str,
|
|
version: &Version,
|
|
additional: &[String],
|
|
) -> String {
|
|
let Version { major, minor, .. } = version;
|
|
let custom_tag = if custom_tag.is_empty() {
|
|
String::new()
|
|
} else {
|
|
format!("-{custom_tag}")
|
|
};
|
|
let additional = additional
|
|
.iter()
|
|
.map(|tag| format!(" -t {image_name}:{tag}{custom_tag}"))
|
|
.collect::<Vec<_>>()
|
|
.join("");
|
|
format!(
|
|
" -t {image_name}:latest{custom_tag} -t {image_name}:{version}{custom_tag} -t {image_name}:{major}.{minor}{custom_tag} -t {image_name}:{major}{custom_tag}{additional}",
|
|
)
|
|
}
|
|
|
|
fn parse_build_args(build_args: &[EnvironmentVar]) -> String {
|
|
build_args
|
|
.iter()
|
|
.map(|p| {
|
|
if p.value.starts_with(QUOTE_PATTERN)
|
|
&& p.value.ends_with(QUOTE_PATTERN)
|
|
{
|
|
// If the value already wrapped in quotes, don't wrap it again
|
|
format!(" --build-arg {}={}", p.variable, p.value)
|
|
} else {
|
|
format!(" --build-arg {}=\"{}\"", p.variable, p.value)
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join("")
|
|
}
|
|
|
|
fn parse_secret_args(
|
|
secret_args: &[EnvironmentVar],
|
|
skip_secret_interp: bool,
|
|
) -> anyhow::Result<String> {
|
|
let periphery_config = periphery_config();
|
|
Ok(
|
|
secret_args
|
|
.iter()
|
|
.map(|EnvironmentVar { variable, value }| {
|
|
if variable.is_empty() {
|
|
return Err(anyhow!("secret variable cannot be empty string"))
|
|
} else if variable.contains('=') {
|
|
return Err(anyhow!("invalid variable {variable}. variable cannot contain '='"))
|
|
}
|
|
let value = if skip_secret_interp {
|
|
value.to_string()
|
|
} else {
|
|
svi::interpolate_variables(
|
|
value,
|
|
&periphery_config.secrets,
|
|
svi::Interpolator::DoubleBrackets,
|
|
true,
|
|
)
|
|
.context(
|
|
"failed to interpolate periphery secrets into build secrets",
|
|
)?.0
|
|
};
|
|
std::env::set_var(variable, value);
|
|
anyhow::Ok(format!(" --secret id={variable}"))
|
|
})
|
|
.collect::<anyhow::Result<Vec<_>>>()?
|
|
.join(""),
|
|
)
|
|
}
|
|
|
|
fn cleanup_secret_env_vars(secret_args: &[EnvironmentVar]) {
|
|
secret_args.iter().for_each(
|
|
|EnvironmentVar { variable, .. }| std::env::remove_var(variable),
|
|
)
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<super::Args> for PruneBuilders {
|
|
#[instrument(name = "PruneBuilders", skip_all)]
|
|
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
|
let command = String::from("docker builder prune -a -f");
|
|
Ok(run_komodo_command("Prune Builders", None, command).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<super::Args> for PruneBuildx {
|
|
#[instrument(name = "PruneBuildx", skip_all)]
|
|
async fn resolve(self, _: &super::Args) -> serror::Result<Log> {
|
|
let command = String::from("docker buildx prune -a -f");
|
|
Ok(run_komodo_command("Prune Buildx", None, command).await)
|
|
}
|
|
}
|