Files
komodo/bin/periphery/src/api/deploy.rs
T
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

202 lines
4.9 KiB
Rust

use anyhow::Context;
use command::run_komodo_command;
use formatting::format_serror;
use komodo_client::entities::{
deployment::{
conversions_from_str, extract_registry_domain, Conversion,
Deployment, DeploymentConfig, DeploymentImage, RestartMode,
},
environment_vars_from_str, to_komodo_name,
update::Log,
EnvironmentVar,
};
use periphery_client::api::container::{Deploy, RemoveContainer};
use resolver_api::Resolve;
use crate::{
config::periphery_config,
docker::{docker_login, pull_image},
helpers::{parse_extra_args, parse_labels},
State,
};
impl Resolve<Deploy> for State {
#[instrument(
name = "Deploy",
skip(self, core_replacers, registry_token)
)]
async fn resolve(
&self,
Deploy {
deployment,
stop_signal,
stop_time,
registry_token,
replacers: core_replacers,
}: Deploy,
_: (),
) -> anyhow::Result<Log> {
let image = if let DeploymentImage::Image { image } =
&deployment.config.image
{
if image.is_empty() {
return Ok(Log::error(
"get image",
String::from("deployment does not have image attached"),
));
}
image
} else {
return Ok(Log::error(
"get image",
String::from("deployment does not have image attached"),
));
};
if let Err(e) = docker_login(
&extract_registry_domain(image)?,
&deployment.config.image_registry_account,
registry_token.as_deref(),
)
.await
{
return Ok(Log::error(
"docker login",
format_serror(
&e.context("failed to login to docker registry").into(),
),
));
}
let _ = pull_image(image).await;
debug!("image pulled");
let _ = State
.resolve(
RemoveContainer {
name: deployment.name.clone(),
signal: stop_signal,
time: stop_time,
},
(),
)
.await;
debug!("container stopped and removed");
let command = docker_run_command(&deployment, image)
.context("Unable to generate valid docker run command")?;
debug!("docker run command: {command}");
if deployment.config.skip_secret_interp {
Ok(run_komodo_command("docker run", None, command).await)
} else {
let command = svi::interpolate_variables(
&command,
&periphery_config().secrets,
svi::Interpolator::DoubleBrackets,
true,
)
.context(
"failed to interpolate secrets into docker run command",
);
let (command, mut replacers) = match command {
Ok(res) => res,
Err(e) => {
return Ok(Log::error("docker run", format!("{e:?}")));
}
};
replacers.extend(core_replacers);
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);
Ok(log)
}
}
}
fn docker_run_command(
Deployment {
name,
config:
DeploymentConfig {
volumes,
ports,
network,
command,
restart,
environment,
labels,
extra_args,
..
},
..
}: &Deployment,
image: &str,
) -> anyhow::Result<String> {
let name = to_komodo_name(name);
let ports = parse_conversions(
&conversions_from_str(ports).context("Invalid ports")?,
"-p",
);
let volumes = parse_conversions(
&conversions_from_str(volumes).context("Invalid volumes")?,
"-v",
);
let network = parse_network(network);
let restart = parse_restart(restart);
let environment = parse_environment(
&environment_vars_from_str(environment)
.context("Invalid environment")?,
);
let labels = parse_labels(
&environment_vars_from_str(labels).context("Invalid labels")?,
);
let command = parse_command(command);
let extra_args = parse_extra_args(extra_args);
let command = format!("docker run -d --name {name}{ports}{volumes}{network}{restart}{environment}{labels}{extra_args} {image}{command}");
Ok(command)
}
fn parse_conversions(
conversions: &[Conversion],
flag: &str,
) -> String {
conversions
.iter()
.map(|p| format!(" {flag} {}:{}", p.local, p.container))
.collect::<Vec<_>>()
.join("")
}
fn parse_environment(environment: &[EnvironmentVar]) -> String {
environment
.iter()
.map(|p| format!(" --env {}=\"{}\"", p.variable, p.value))
.collect::<Vec<_>>()
.join("")
}
fn parse_network(network: &str) -> String {
format!(" --network {network}")
}
fn parse_restart(restart: &RestartMode) -> String {
let restart = match restart {
RestartMode::OnFailure => "on-failure:10".to_string(),
_ => restart.to_string(),
};
format!(" --restart {restart}")
}
fn parse_command(command: &str) -> String {
if command.is_empty() {
String::new()
} else {
format!(" {command}")
}
}