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, }, parsers::QUOTE_PATTERN, }; 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 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 { 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, false).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, false).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 { 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::>() .join("") } fn parse_environment(environment: &[EnvironmentVar]) -> String { environment .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!(" --env {}={}", p.variable, p.value) } else { format!(" --env {}=\"{}\"", p.variable, p.value) } }) .collect::>() .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}") } }