shrink periphery implementation

This commit is contained in:
mbecker20
2024-07-01 01:19:25 -07:00
parent c3f1557b83
commit 0e81d17860
9 changed files with 450 additions and 548 deletions

View File

@@ -198,11 +198,6 @@ impl TryFrom<Build> for monitor_client::entities::build::Build {
id: value.id,
name: value.name,
description: value.description,
// permissions: value
// .permissions
// .into_iter()
// .map(|(id, p)| (id, p.into()))
// .collect(),
updated_at: unix_from_monitor_ts(&value.updated_at)?,
tags: Vec::new(),
info: BuildInfo {
@@ -233,6 +228,7 @@ impl TryFrom<Build> for monitor_client::entities::build::Build {
build_path,
dockerfile_path,
build_args,
secret_args: Default::default(),
extra_args,
use_buildx,
labels: Default::default(),

View File

@@ -134,6 +134,7 @@ impl From<BuildConfig>
.into_iter()
.map(Into::into)
.collect(),
secret_args: Default::default(),
labels: value.labels.into_iter().map(Into::into).collect(),
extra_args: value.extra_args,
use_buildx: value.use_buildx,

View File

@@ -1,41 +1,159 @@
use anyhow::Context;
use command::run_monitor_command;
use formatting::format_serror;
use monitor_client::entities::{
server::docker_image::ImageSummary, update::Log,
};
use periphery_client::api::build::{
Build, GetImageList, PruneImages,
build::{Build, BuildConfig},
get_image_name, optional_string,
server::docker_image::ImageSummary,
to_monitor_name,
update::Log,
EnvironmentVar, Version,
};
use periphery_client::api::build::{self, GetImageList, PruneImages};
use resolver_api::Resolve;
use crate::{
docker::{self, client::docker_client},
config::periphery_config,
docker::{
client::docker_client, docker_login, parse_extra_args,
parse_labels,
},
State,
};
impl Resolve<Build> for State {
impl Resolve<build::Build> for State {
#[instrument(
name = "Build",
skip(self, registry_token, replacers, aws_ecr)
skip(self, registry_token, core_replacers, aws_ecr)
)]
async fn resolve(
&self,
Build {
build::Build {
build,
registry_token,
replacers,
aws_ecr,
}: Build,
registry_token,
replacers: core_replacers,
}: build::Build,
_: (),
) -> anyhow::Result<Vec<Log>> {
docker::build::build(
&build,
registry_token,
replacers,
let Build {
name,
config:
BuildConfig {
version,
skip_secret_interp,
build_path,
dockerfile_path,
build_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,
registry_token.as_deref(),
aws_ecr.as_ref(),
)
.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_monitor_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, |_| aws_ecr)
.context("failed to make image name")?;
let build_args = parse_build_args(build_args);
let labels = parse_labels(labels);
let extra_args = parse_extra_args(extra_args);
let buildx = if *use_buildx { " buildx" } else { "" };
let image_tags = image_tags(&image_name, version);
let push_command = should_push
.then(|| {
format!(" && docker image push --all-tags {image_name}")
})
.unwrap_or_default();
// Construct command
let command = format!(
"cd {} && docker{buildx} build{build_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
build_dir.display()
);
if *skip_secret_interp {
let build_log =
run_monitor_command("docker build", command).await;
info!("finished building docker image");
logs.push(build_log);
} else {
// Interpolate any missing secrets
let (command, mut replacers) = svi::interpolate_variables(
&command,
&periphery_config().secrets,
svi::Interpolator::DoubleBrackets,
true,
)
.context(
"failed to interpolate secrets into docker build command",
)?;
replacers.extend(core_replacers);
let mut build_log =
run_monitor_command("docker build", command).await;
build_log.command =
svi::replace_in_string(&build_log.command, &replacers);
build_log.stdout =
svi::replace_in_string(&build_log.stdout, &replacers);
build_log.stderr =
svi::replace_in_string(&build_log.stderr, &replacers);
logs.push(build_log);
}
Ok(logs)
}
}
fn image_tags(image_name: &str, version: &Version) -> String {
let Version { major, minor, .. } = version;
format!(
" -t {image_name}:latest -t {image_name}:{version} -t {image_name}:{major}.{minor} -t {image_name}:{major}",
)
}
fn parse_build_args(build_args: &[EnvironmentVar]) -> String {
build_args
.iter()
.map(|p| format!(" --build-arg {}=\"{}\"", p.variable, p.value))
.collect::<Vec<_>>()
.join("")
}
//
impl Resolve<GetImageList> for State {
@@ -58,6 +176,7 @@ impl Resolve<PruneImages> for State {
_: PruneImages,
_: (),
) -> anyhow::Result<Log> {
Ok(docker::build::prune_images().await)
let command = String::from("docker image prune -a -f");
Ok(run_monitor_command("prune images", command).await)
}
}

View File

@@ -1,13 +1,26 @@
use anyhow::anyhow;
use anyhow::{anyhow, Context};
use command::run_monitor_command;
use formatting::format_serror;
use monitor_client::entities::{
deployment::{ContainerSummary, DockerContainerStats},
deployment::{
ContainerSummary, Conversion, Deployment, DeploymentConfig,
DeploymentImage, DockerContainerStats, RestartMode,
TerminationSignal,
},
to_monitor_name,
update::Log,
EnvironmentVar, SearchCombinator,
};
use periphery_client::api::container::*;
use resolver_api::Resolve;
use run_command::async_run_command;
use crate::{
docker::{self, client::docker_client},
config::periphery_config,
docker::{
client::docker_client, docker_login, parse_extra_args,
parse_labels,
},
State,
};
@@ -34,10 +47,11 @@ impl Resolve<GetContainerLog> for State {
#[instrument(name = "GetContainerLog", level = "debug", skip(self))]
async fn resolve(
&self,
req: GetContainerLog,
GetContainerLog { name, tail }: GetContainerLog,
_: (),
) -> anyhow::Result<Log> {
Ok(docker::container::container_log(&req.name, req.tail).await)
let command = format!("docker logs {name} --tail {tail}");
Ok(run_monitor_command("get container log", command).await)
}
}
@@ -59,12 +73,21 @@ impl Resolve<GetContainerLogSearch> for State {
}: GetContainerLogSearch,
_: (),
) -> anyhow::Result<Log> {
Ok(
docker::container::container_log_search(
&name, &terms, combinator, invert,
)
.await,
)
let maybe_invert = invert.then_some(" -v").unwrap_or_default();
let grep = match combinator {
SearchCombinator::Or => {
format!("grep{maybe_invert} -E '{}'", terms.join("|"))
}
SearchCombinator::And => {
format!(
"grep{maybe_invert} -P '^(?=.*{})'",
terms.join(")(?=.*")
)
}
};
let command =
format!("docker logs {name} --tail 5000 2>&1 | {grep}");
Ok(run_monitor_command("get container log grep", command).await)
}
}
@@ -82,8 +105,7 @@ impl Resolve<GetContainerStats> for State {
_: (),
) -> anyhow::Result<DockerContainerStats> {
let error = anyhow!("no stats matching {}", req.name);
let mut stats =
docker::container::container_stats(Some(req.name)).await?;
let mut stats = container_stats(Some(req.name)).await?;
let stats = stats.pop().ok_or(error)?;
Ok(stats)
}
@@ -102,7 +124,7 @@ impl Resolve<GetContainerStatsList> for State {
_: GetContainerStatsList,
_: (),
) -> anyhow::Result<Vec<DockerContainerStats>> {
docker::container::container_stats(None).await
container_stats(None).await
}
}
@@ -112,10 +134,16 @@ impl Resolve<StartContainer> for State {
#[instrument(name = "StartContainer", skip(self))]
async fn resolve(
&self,
req: StartContainer,
StartContainer { name }: StartContainer,
_: (),
) -> anyhow::Result<Log> {
Ok(docker::container::start_container(&req.name).await)
Ok(
run_monitor_command(
"docker start",
format!("docker start {name}"),
)
.await,
)
}
}
@@ -125,15 +153,26 @@ impl Resolve<StopContainer> for State {
#[instrument(name = "StopContainer", skip(self))]
async fn resolve(
&self,
req: StopContainer,
StopContainer { name, signal, time }: StopContainer,
_: (),
) -> anyhow::Result<Log> {
Ok(
docker::container::stop_container(
&req.name, req.signal, req.time,
)
.await,
)
let command = stop_container_command(&name, signal, time);
let log = run_monitor_command("docker stop", command).await;
if log.stderr.contains("unknown flag: --signal") {
let command = stop_container_command(&name, None, time);
let mut log = run_monitor_command("docker stop", command).await;
log.stderr = format!(
"old docker version: unable to use --signal flag{}",
if !log.stderr.is_empty() {
format!("\n\n{}", log.stderr)
} else {
String::new()
}
);
Ok(log)
} else {
Ok(log)
}
}
}
@@ -143,15 +182,31 @@ impl Resolve<RemoveContainer> for State {
#[instrument(name = "RemoveContainer", skip(self))]
async fn resolve(
&self,
req: RemoveContainer,
RemoveContainer { name, signal, time }: RemoveContainer,
_: (),
) -> anyhow::Result<Log> {
Ok(
docker::container::stop_and_remove_container(
&req.name, req.signal, req.time,
)
.await,
)
let stop_command = stop_container_command(&name, signal, time);
let command =
format!("{stop_command} && docker container rm {name}");
let log =
run_monitor_command("docker stop and remove", 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_monitor_command("docker stop", command).await;
log.stderr = format!(
"old docker version: unable to use --signal flag{}",
if !log.stderr.is_empty() {
format!("\n\n{}", log.stderr)
} else {
String::new()
}
);
Ok(log)
} else {
Ok(log)
}
}
}
@@ -161,16 +216,15 @@ impl Resolve<RenameContainer> for State {
#[instrument(name = "RenameContainer", skip(self))]
async fn resolve(
&self,
req: RenameContainer,
RenameContainer {
curr_name,
new_name,
}: RenameContainer,
_: (),
) -> anyhow::Result<Log> {
Ok(
docker::container::rename_container(
&req.curr_name,
&req.new_name,
)
.await,
)
let new = to_monitor_name(&new_name);
let command = format!("docker rename {curr_name} {new}");
Ok(run_monitor_command("docker rename", command).await)
}
}
@@ -183,14 +237,18 @@ impl Resolve<PruneContainers> for State {
_: PruneContainers,
_: (),
) -> anyhow::Result<Log> {
Ok(docker::container::prune_containers().await)
let command = String::from("docker container prune -f");
Ok(run_monitor_command("prune containers", command).await)
}
}
//
impl Resolve<Deploy> for State {
#[instrument(name = "Deploy", skip(self, replacers))]
#[instrument(
name = "Deploy",
skip(self, core_replacers, aws_ecr, registry_token)
)]
async fn resolve(
&self,
Deploy {
@@ -198,24 +256,206 @@ impl Resolve<Deploy> for State {
stop_signal,
stop_time,
registry_token,
replacers,
replacers: core_replacers,
aws_ecr,
}: Deploy,
_: (),
) -> anyhow::Result<Log> {
let res = docker::container::deploy(
&deployment,
stop_signal
.unwrap_or(deployment.config.termination_signal)
.into(),
stop_time
.unwrap_or(deployment.config.termination_timeout)
.into(),
registry_token,
replacers,
if let Err(e) = docker_login(
&deployment.config.image_registry,
registry_token.as_deref(),
aws_ecr.as_ref(),
)
.await;
Ok(res)
.await
{
return Ok(Log::error(
"docker login",
format_serror(
&e.context("failed to login to docker registry").into(),
),
));
}
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"),
));
};
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);
debug!("docker run command: {command}");
if deployment.config.skip_secret_interp {
Ok(run_monitor_command("docker run", 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",
);
if let Err(e) = command {
return Ok(Log::error("docker run", format!("{e:?}")));
}
let (command, mut replacers) = command.unwrap();
replacers.extend(core_replacers);
let mut log = run_monitor_command("docker run", 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,
) -> String {
let name = to_monitor_name(name);
let ports = parse_conversions(ports, "-p");
let volumes = volumes.to_owned();
let volumes = parse_conversions(&volumes, "-v");
let network = parse_network(network);
let restart = parse_restart(restart);
let environment = parse_environment(environment);
let labels = parse_labels(labels);
let command = parse_command(command);
let extra_args = parse_extra_args(extra_args);
format!("docker run -d --name {name}{ports}{volumes}{network}{restart}{environment}{labels}{extra_args} {image}{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}")
}
}
//
async fn container_stats(
container_name: Option<String>,
) -> anyhow::Result<Vec<DockerContainerStats>> {
let format = "--format \"{{ json . }}\"";
let container_name = match container_name {
Some(name) => format!(" {name}"),
None => "".to_string(),
};
let command =
format!("docker stats{container_name} --no-stream {format}");
let output = async_run_command(&command).await;
if output.success() {
let res = output
.stdout
.split('\n')
.filter(|e| !e.is_empty())
.map(|e| {
let parsed = serde_json::from_str(e)
.context(format!("failed at parsing entry {e}"))?;
Ok(parsed)
})
.collect::<anyhow::Result<Vec<DockerContainerStats>>>()?;
Ok(res)
} else {
Err(anyhow!("{}", output.stderr.replace('\n', "")))
}
}
#[instrument]
async fn pull_image(image: &str) -> Log {
let command = format!("docker pull {image}");
run_monitor_command("docker pull", command).await
}
fn stop_container_command(
container_name: &str,
signal: Option<TerminationSignal>,
time: Option<i32>,
) -> String {
let container_name = to_monitor_name(container_name);
let signal = signal
.map(|signal| format!(" --signal {signal}"))
.unwrap_or_default();
let time = time
.map(|time| format!(" --time {time}"))
.unwrap_or_default();
format!("docker stop{signal}{time} {container_name}")
}

View File

@@ -1,142 +0,0 @@
use anyhow::Context;
use command::run_monitor_command;
use formatting::format_serror;
use monitor_client::entities::{
build::{Build, BuildConfig},
config::core::AwsEcrConfig,
get_image_name, optional_string, to_monitor_name,
update::Log,
EnvironmentVar, Version,
};
use crate::config::periphery_config;
use super::{docker_login, parse_extra_args, parse_labels};
#[instrument]
pub async fn prune_images() -> Log {
let command = String::from("docker image prune -a -f");
run_monitor_command("prune images", command).await
}
#[instrument(skip(registry_token, core_replacers))]
pub async fn build(
build: &Build,
registry_token: Option<String>,
core_replacers: Vec<(String, String)>,
aws_ecr: Option<&AwsEcrConfig>,
) -> anyhow::Result<Vec<Log>> {
let Build {
name,
config:
BuildConfig {
version,
skip_secret_interp,
build_path,
dockerfile_path,
build_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,
registry_token.as_deref(),
aws_ecr,
)
.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_monitor_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, |_| aws_ecr.cloned())
.context("failed to make image name")?;
let build_args = parse_build_args(build_args);
let labels = parse_labels(labels);
let extra_args = parse_extra_args(extra_args);
let buildx = if *use_buildx { " buildx" } else { "" };
let image_tags = image_tags(&image_name, version);
let push_command = should_push
.then(|| format!(" && docker image push --all-tags {image_name}"))
.unwrap_or_default();
// Construct command
let command = format!(
"cd {} && docker{buildx} build{build_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
build_dir.display()
);
if *skip_secret_interp {
let build_log =
run_monitor_command("docker build", command).await;
info!("finished building docker image");
logs.push(build_log);
} else {
// Interpolate any missing secrets
let (command, mut replacers) = svi::interpolate_variables(
&command,
&periphery_config().secrets,
svi::Interpolator::DoubleBrackets,
true,
)
.context(
"failed to interpolate secrets into docker build command",
)?;
replacers.extend(core_replacers);
let mut build_log =
run_monitor_command("docker build", command).await;
build_log.command =
svi::replace_in_string(&build_log.command, &replacers);
build_log.stdout =
svi::replace_in_string(&build_log.stdout, &replacers);
build_log.stderr =
svi::replace_in_string(&build_log.stderr, &replacers);
logs.push(build_log);
}
Ok(logs)
}
fn image_tags(image_name: &str, version: &Version) -> String {
let Version { major, minor, .. } = version;
format!(
" -t {image_name}:latest -t {image_name}:{version} -t {image_name}:{major}.{minor} -t {image_name}:{major}",
)
}
fn parse_build_args(build_args: &[EnvironmentVar]) -> String {
build_args
.iter()
.map(|p| format!(" --build-arg {}=\"{}\"", p.variable, p.value))
.collect::<Vec<_>>()
.join("")
}

View File

@@ -1,328 +0,0 @@
use anyhow::{anyhow, Context};
use command::run_monitor_command;
use formatting::format_serror;
use monitor_client::entities::{
config::core::AwsEcrConfig,
deployment::{
Conversion, Deployment, DeploymentConfig, DeploymentImage,
DockerContainerStats, RestartMode, TerminationSignal,
},
to_monitor_name,
update::Log,
EnvironmentVar, SearchCombinator,
};
use run_command::async_run_command;
use crate::config::periphery_config;
use super::{docker_login, parse_extra_args, parse_labels};
#[instrument(level = "debug")]
pub async fn container_log(container_name: &str, tail: u64) -> Log {
let command = format!("docker logs {container_name} --tail {tail}");
run_monitor_command("get container log", command).await
}
#[instrument(level = "debug")]
pub async fn container_log_search(
container_name: &str,
terms: &[String],
combinator: SearchCombinator,
invert: bool,
) -> Log {
let maybe_invert = invert.then_some(" -v").unwrap_or_default();
let grep = match combinator {
SearchCombinator::Or => {
format!("grep{maybe_invert} -E '{}'", terms.join("|"))
}
SearchCombinator::And => {
format!(
"grep{maybe_invert} -P '^(?=.*{})'",
terms.join(")(?=.*")
)
}
};
let command =
format!("docker logs {container_name} --tail 5000 2>&1 | {grep}");
run_monitor_command("get container log grep", command).await
}
#[instrument(level = "debug")]
pub async fn container_stats(
container_name: Option<String>,
) -> anyhow::Result<Vec<DockerContainerStats>> {
let format = "--format \"{{ json . }}\"";
let container_name = match container_name {
Some(name) => format!(" {name}"),
None => "".to_string(),
};
let command =
format!("docker stats{container_name} --no-stream {format}");
let output = async_run_command(&command).await;
if output.success() {
let res = output
.stdout
.split('\n')
.filter(|e| !e.is_empty())
.map(|e| {
let parsed = serde_json::from_str(e)
.context(format!("failed at parsing entry {e}"))?;
Ok(parsed)
})
.collect::<anyhow::Result<Vec<DockerContainerStats>>>()?;
Ok(res)
} else {
Err(anyhow!("{}", output.stderr.replace('\n', "")))
}
}
#[instrument]
pub async fn prune_containers() -> Log {
let command = String::from("docker container prune -f");
run_monitor_command("prune containers", command).await
}
#[instrument]
pub async fn start_container(container_name: &str) -> Log {
let container_name = to_monitor_name(container_name);
let command = format!("docker start {container_name}");
run_monitor_command("docker start", command).await
}
#[instrument]
pub async fn stop_container(
container_name: &str,
signal: Option<TerminationSignal>,
time: Option<i32>,
) -> Log {
let command = stop_container_command(container_name, signal, time);
let log = run_monitor_command("docker stop", command).await;
if log.stderr.contains("unknown flag: --signal") {
let command = stop_container_command(container_name, None, time);
let mut log = run_monitor_command("docker stop", command).await;
log.stderr = format!(
"old docker version: unable to use --signal flag{}",
if !log.stderr.is_empty() {
format!("\n\n{}", log.stderr)
} else {
String::new()
}
);
log
} else {
log
}
}
#[instrument]
pub async fn stop_and_remove_container(
container_name: &str,
signal: Option<TerminationSignal>,
time: Option<i32>,
) -> Log {
let stop_command =
stop_container_command(container_name, signal, time);
let command =
format!("{stop_command} && docker container rm {container_name}");
let log =
run_monitor_command("docker stop and remove", command).await;
if log.stderr.contains("unknown flag: --signal") {
let stop_command =
stop_container_command(container_name, None, time);
let command = format!(
"{stop_command} && docker container rm {container_name}"
);
let mut log = run_monitor_command("docker stop", command).await;
log.stderr = format!(
"old docker version: unable to use --signal flag{}",
if !log.stderr.is_empty() {
format!("\n\n{}", log.stderr)
} else {
String::new()
}
);
log
} else {
log
}
}
fn stop_container_command(
container_name: &str,
signal: Option<TerminationSignal>,
time: Option<i32>,
) -> String {
let container_name = to_monitor_name(container_name);
let signal = signal
.map(|signal| format!(" --signal {signal}"))
.unwrap_or_default();
let time = time
.map(|time| format!(" --time {time}"))
.unwrap_or_default();
format!("docker stop{signal}{time} {container_name}")
}
#[instrument]
pub async fn rename_container(
curr_name: &str,
new_name: &str,
) -> Log {
let curr = to_monitor_name(curr_name);
let new = to_monitor_name(new_name);
let command = format!("docker rename {curr} {new}");
run_monitor_command("docker rename", command).await
}
#[instrument]
async fn pull_image(image: &str) -> Log {
let command = format!("docker pull {image}");
run_monitor_command("docker pull", command).await
}
#[instrument(skip(registry_token, core_replacers))]
pub async fn deploy(
deployment: &Deployment,
stop_signal: Option<TerminationSignal>,
stop_time: Option<i32>,
registry_token: Option<String>,
core_replacers: Vec<(String, String)>,
aws_ecr: Option<&AwsEcrConfig>,
) -> Log {
if let Err(e) = docker_login(
&deployment.config.image_registry,
registry_token.as_deref(),
aws_ecr,
)
.await
{
return Log::error(
"docker login",
format_serror(
&e.context("failed to login to docker registry").into(),
),
);
}
let image = if let DeploymentImage::Image { image } =
&deployment.config.image
{
if image.is_empty() {
return Log::error(
"get image",
String::from("deployment does not have image attached"),
);
}
image
} else {
return Log::error(
"get image",
String::from("deployment does not have image attached"),
);
};
let _ = pull_image(image).await;
debug!("image pulled");
let _ = stop_and_remove_container(
&deployment.name,
stop_signal,
stop_time,
)
.await;
debug!("container stopped and removed");
let command = docker_run_command(deployment, image);
debug!("docker run command: {command}");
if deployment.config.skip_secret_interp {
run_monitor_command("docker run", 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");
if let Err(e) = command {
return Log::error("docker run", format!("{e:?}"));
}
let (command, mut replacers) = command.unwrap();
replacers.extend(core_replacers);
let mut log = run_monitor_command("docker run", 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);
log
}
}
pub fn docker_run_command(
Deployment {
name,
config:
DeploymentConfig {
volumes,
ports,
network,
command,
restart,
environment,
labels,
extra_args,
..
},
..
}: &Deployment,
image: &str,
) -> String {
let name = to_monitor_name(name);
let ports = parse_conversions(ports, "-p");
let volumes = volumes.to_owned();
let volumes = parse_conversions(&volumes, "-v");
let network = parse_network(network);
let restart = parse_restart(restart);
let environment = parse_environment(environment);
let labels = parse_labels(labels);
let command = parse_command(command);
let extra_args = parse_extra_args(extra_args);
format!("docker run -d --name {name}{ports}{volumes}{network}{restart}{environment}{labels}{extra_args} {image}{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}")
}
}

View File

@@ -10,9 +10,7 @@ use run_command::async_run_command;
use crate::helpers::{get_docker_token, get_github_token};
pub mod build;
pub mod client;
pub mod container;
pub mod network;
/// Returns whether build result should be pushed after build

View File

@@ -59,7 +59,7 @@ pub struct DeleteAlerter {
/// Update the alerter at the given id, and return the updated alerter. Response: [Alerter].
///
/// Note. This method updates only the fields which are set in the [PartialAlerterConfig],
/// Note. This method updates only the fields which are set in the [PartialAlerterConfig][crate::entities::alerter::PartialAlerterConfig],
/// effectively merging diffs into the final document. This is helpful when multiple users are using
/// the same resources concurrently by ensuring no unintentional
/// field changes occur from out of date local state.

View File

@@ -142,7 +142,9 @@ pub struct BuildConfig {
#[builder(default)]
pub extra_args: Vec<String>,
/// Docker build arguments
/// Docker build arguments.
///
/// These values are visible in the final image by running `docker inspect`.
#[serde(
default,
deserialize_with = "super::env_vars_deserializer"
@@ -154,6 +156,21 @@ pub struct BuildConfig {
#[builder(default)]
pub build_args: Vec<EnvironmentVar>,
/// Secret arguments.
///
/// These values remain hidden in the final image by using
/// docker secret mounts. See `<https://docs.docker.com/build/building/secrets>`.
#[serde(
default,
deserialize_with = "super::env_vars_deserializer"
)]
#[partial_attr(serde(
default,
deserialize_with = "super::option_env_vars_deserializer"
))]
#[builder(default)]
pub secret_args: Vec<EnvironmentVar>,
/// Docker labels
#[serde(
default,
@@ -203,6 +220,7 @@ impl Default for BuildConfig {
build_path: default_build_path(),
dockerfile_path: default_dockerfile_path(),
build_args: Default::default(),
secret_args: Default::default(),
labels: Default::default(),
extra_args: Default::default(),
use_buildx: Default::default(),