docker service create

This commit is contained in:
mbecker20
2025-12-04 00:56:02 -08:00
parent 8326286a3c
commit 98e5e84bc4
26 changed files with 1226 additions and 682 deletions

View File

@@ -225,7 +225,7 @@ impl Resolve<ExecuteArgs> for Deploy {
match periphery_client(&server)
.await?
.request(api::container::Deploy {
.request(api::container::RunContainer {
deployment,
stop_signal: self.stop_signal,
stop_time: self.stop_time,

View File

@@ -20,7 +20,7 @@ use komodo_client::{
user::User,
},
};
use periphery_client::api::compose::*;
use periphery_client::api::{DeployStackResponse, compose::*};
use resolver_api::Resolve;
use uuid::Uuid;
@@ -165,14 +165,14 @@ impl Resolve<ExecuteArgs> for DeployStack {
Default::default()
};
let ComposeUpResponse {
let DeployStackResponse {
logs,
deployed,
services,
file_contents,
missing_files,
remote_errors,
compose_config,
merged_config,
commit_hash,
commit_message,
} = periphery_client(&server)
@@ -219,7 +219,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
})
.collect(),
),
compose_config,
merged_config,
commit_hash.clone(),
commit_message.clone(),
)

View File

@@ -35,10 +35,10 @@ use crate::{
mod helpers;
use helpers::*;
impl Resolve<super::Args> for GetDockerfileContentsOnHost {
impl Resolve<crate::api::Args> for GetDockerfileContentsOnHost {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<GetDockerfileContentsOnHostResponse> {
let GetDockerfileContentsOnHost {
name,
@@ -75,7 +75,7 @@ impl Resolve<super::Args> for GetDockerfileContentsOnHost {
}
}
impl Resolve<super::Args> for WriteDockerfileContentsToHost {
impl Resolve<crate::api::Args> for WriteDockerfileContentsToHost {
#[instrument(
"WriteDockerfileContentsToHost",
skip_all,
@@ -87,7 +87,10 @@ impl Resolve<super::Args> for WriteDockerfileContentsToHost {
dockerfile_path = &self.dockerfile_path,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let WriteDockerfileContentsToHost {
name,
build_path,
@@ -123,7 +126,7 @@ impl Resolve<super::Args> for WriteDockerfileContentsToHost {
}
}
impl Resolve<super::Args> for build::Build {
impl Resolve<crate::api::Args> for build::Build {
#[instrument(
"Build",
skip_all,
@@ -136,7 +139,7 @@ impl Resolve<super::Args> for build::Build {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<Vec<Log>> {
let build::Build {
mut build,
@@ -339,7 +342,7 @@ impl Resolve<super::Args> for build::Build {
//
impl Resolve<super::Args> for PruneBuilders {
impl Resolve<crate::api::Args> for PruneBuilders {
#[instrument(
"PruneBuilders",
skip_all,
@@ -348,7 +351,10 @@ impl Resolve<super::Args> for PruneBuilders {
core = args.core,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = String::from("docker builder prune -a -f");
Ok(
run_komodo_standard_command("Prune Builders", None, command)
@@ -359,7 +365,7 @@ impl Resolve<super::Args> for PruneBuilders {
//
impl Resolve<super::Args> for PruneBuildx {
impl Resolve<crate::api::Args> for PruneBuildx {
#[instrument(
"PruneBuildx",
skip_all,
@@ -368,7 +374,10 @@ impl Resolve<super::Args> for PruneBuildx {
core = args.core,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = String::from("docker buildx prune -a -f");
Ok(
run_komodo_standard_command("Prune Buildx", None, command)

View File

@@ -14,7 +14,7 @@ use komodo_client::entities::{
update::Log,
};
use periphery_client::api::{
compose::ComposeUpResponse, git::PullOrCloneRepo,
DeployStackResponse, git::PullOrCloneRepo,
};
use resolver_api::Resolve;
use tokio::fs;
@@ -89,7 +89,7 @@ pub fn env_file_args(
pub async fn compose_down(
project: &str,
services: &[String],
res: &mut ComposeUpResponse,
res: &mut DeployStackResponse,
) -> anyhow::Result<()> {
let docker_compose = docker_compose();
let service_args = if services.is_empty() {
@@ -191,7 +191,7 @@ pub async fn pull_or_clone_stack(
pub async fn validate_files(
stack: &Stack,
run_directory: &Path,
res: &mut ComposeUpResponse,
res: &mut DeployStackResponse,
) {
let file_paths = stack
.all_file_dependencies()

View File

@@ -20,7 +20,7 @@ use komodo_client::{
},
parsers::parse_multiline_command,
};
use periphery_client::api::compose::*;
use periphery_client::api::{DeployStackResponse, compose::*};
use resolver_api::Resolve;
use shell_escape::unix::escape;
use tracing::Instrument;
@@ -36,8 +36,11 @@ mod write;
use helpers::*;
impl Resolve<super::Args> for GetComposeLog {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
impl Resolve<crate::api::Args> for GetComposeLog {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Log> {
let GetComposeLog {
project,
services,
@@ -61,8 +64,11 @@ impl Resolve<super::Args> for GetComposeLog {
}
}
impl Resolve<super::Args> for GetComposeLogSearch {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
impl Resolve<crate::api::Args> for GetComposeLogSearch {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Log> {
let GetComposeLogSearch {
project,
services,
@@ -91,10 +97,10 @@ impl Resolve<super::Args> for GetComposeLogSearch {
//
impl Resolve<super::Args> for GetComposeContentsOnHost {
impl Resolve<crate::api::Args> for GetComposeContentsOnHost {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<GetComposeContentsOnHostResponse> {
let GetComposeContentsOnHost {
name,
@@ -146,7 +152,7 @@ impl Resolve<super::Args> for GetComposeContentsOnHost {
//
impl Resolve<super::Args> for WriteComposeContentsToHost {
impl Resolve<crate::api::Args> for WriteComposeContentsToHost {
#[instrument(
"WriteComposeContentsToHost",
skip_all,
@@ -158,7 +164,10 @@ impl Resolve<super::Args> for WriteComposeContentsToHost {
file_path = self.file_path,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let WriteComposeContentsToHost {
name,
run_directory,
@@ -188,7 +197,7 @@ impl Resolve<super::Args> for WriteComposeContentsToHost {
//
impl Resolve<super::Args> for WriteCommitComposeContents {
impl Resolve<crate::api::Args> for WriteCommitComposeContents {
#[instrument(
"WriteCommitComposeContents",
skip_all,
@@ -202,7 +211,7 @@ impl Resolve<super::Args> for WriteCommitComposeContents {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<RepoExecutionResponse> {
let WriteCommitComposeContents {
stack,
@@ -243,7 +252,7 @@ impl Resolve<super::Args> for WriteCommitComposeContents {
//
impl Resolve<super::Args> for ComposePull {
impl Resolve<crate::api::Args> for ComposePull {
#[instrument(
"ComposePull",
skip_all,
@@ -256,7 +265,7 @@ impl Resolve<super::Args> for ComposePull {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<ComposePullResponse> {
let ComposePull {
mut stack,
@@ -362,7 +371,7 @@ impl Resolve<super::Args> for ComposePull {
//
impl Resolve<super::Args> for ComposeUp {
impl Resolve<crate::api::Args> for ComposeUp {
#[instrument(
"ComposeUp",
skip_all,
@@ -376,8 +385,8 @@ impl Resolve<super::Args> for ComposeUp {
)]
async fn resolve(
self,
args: &super::Args,
) -> anyhow::Result<ComposeUpResponse> {
args: &crate::api::Args,
) -> anyhow::Result<DeployStackResponse> {
let ComposeUp {
mut stack,
repo,
@@ -387,7 +396,13 @@ impl Resolve<super::Args> for ComposeUp {
mut replacers,
} = self;
let mut res = ComposeUpResponse::default();
if !stack.config.swarm_id.is_empty() {
return Err(anyhow!(
"This method should only be called for Compose Stacks. This is an internal error and should not happen."
));
}
let mut res = DeployStackResponse::default();
let mut interpolator =
Interpolator::new(None, &periphery_config().secrets);
@@ -502,8 +517,8 @@ impl Resolve<super::Args> for ComposeUp {
let compose =
serde_yaml_ng::from_str::<ComposeFile>(&config_log.stdout)
.context("Failed to parse compose contents")?;
// Record sanitized compose config output
res.compose_config = Some(config_log.stdout);
// Store sanitized compose config output
res.merged_config = Some(config_log.stdout);
for (
service_name,
ComposeService {
@@ -660,7 +675,7 @@ impl Resolve<super::Args> for ComposeUp {
//
impl Resolve<super::Args> for ComposeExecution {
impl Resolve<crate::api::Args> for ComposeExecution {
#[instrument(
"ComposeExecution",
skip_all,
@@ -671,7 +686,10 @@ impl Resolve<super::Args> for ComposeExecution {
command = self.command,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let ComposeExecution { project, command } = self;
let docker_compose = docker_compose();
let log = run_komodo_standard_command(
@@ -686,7 +704,7 @@ impl Resolve<super::Args> for ComposeExecution {
//
impl Resolve<super::Args> for ComposeRun {
impl Resolve<crate::api::Args> for ComposeRun {
#[instrument(
"ComposeRun",
skip_all,
@@ -698,7 +716,10 @@ impl Resolve<super::Args> for ComposeRun {
service = &self.service
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let ComposeRun {
mut stack,
repo,

View File

@@ -7,9 +7,8 @@ use komodo_client::entities::{
stack::Stack, to_path_compatible_name, update::Log,
};
use periphery_client::api::{
compose::{
ComposePullResponse, ComposeRunResponse, ComposeUpResponse,
},
DeployStackResponse,
compose::{ComposePullResponse, ComposeRunResponse},
git::{CloneRepo, PullOrCloneRepo},
};
use resolver_api::Resolve;
@@ -24,7 +23,7 @@ pub trait WriteStackRes {
fn set_commit_message(&mut self, _message: Option<String>) {}
}
impl WriteStackRes for &mut ComposeUpResponse {
impl WriteStackRes for &mut DeployStackResponse {
fn logs(&mut self) -> &mut Vec<Log> {
&mut self.logs
}

View File

@@ -19,16 +19,18 @@ use crate::{
state::docker_client,
};
mod run;
// ======
// READ
// ======
//
impl Resolve<super::Args> for InspectContainer {
impl Resolve<crate::api::Args> for InspectContainer {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<Container> {
let client = docker_client().load();
let client = client
@@ -41,8 +43,11 @@ impl Resolve<super::Args> for InspectContainer {
//
impl Resolve<super::Args> for GetContainerLog {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
impl Resolve<crate::api::Args> for GetContainerLog {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Log> {
let GetContainerLog {
name,
tail,
@@ -64,8 +69,11 @@ impl Resolve<super::Args> for GetContainerLog {
//
impl Resolve<super::Args> for GetContainerLogSearch {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
impl Resolve<crate::api::Args> for GetContainerLogSearch {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Log> {
let GetContainerLogSearch {
name,
terms,
@@ -95,10 +103,10 @@ impl Resolve<super::Args> for GetContainerLogSearch {
//
impl Resolve<super::Args> for GetContainerStats {
impl Resolve<crate::api::Args> for GetContainerStats {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<ContainerStats> {
let mut stats = get_container_stats(Some(self.name)).await?;
let stats =
@@ -109,10 +117,10 @@ impl Resolve<super::Args> for GetContainerStats {
//
impl Resolve<super::Args> for GetFullContainerStats {
impl Resolve<crate::api::Args> for GetFullContainerStats {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<FullContainerStats> {
let client = docker_client().load();
let client = client
@@ -125,10 +133,10 @@ impl Resolve<super::Args> for GetFullContainerStats {
//
impl Resolve<super::Args> for GetContainerStatsList {
impl Resolve<crate::api::Args> for GetContainerStatsList {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<Vec<ContainerStats>> {
get_container_stats(None).await
}
@@ -138,7 +146,7 @@ impl Resolve<super::Args> for GetContainerStatsList {
// ACTIONS
// =========
impl Resolve<super::Args> for StartContainer {
impl Resolve<crate::api::Args> for StartContainer {
#[instrument(
"StartContainer",
skip_all,
@@ -148,7 +156,10 @@ impl Resolve<super::Args> for StartContainer {
container = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
Ok(
run_komodo_standard_command(
"Docker Start",
@@ -162,7 +173,7 @@ impl Resolve<super::Args> for StartContainer {
//
impl Resolve<super::Args> for RestartContainer {
impl Resolve<crate::api::Args> for RestartContainer {
#[instrument(
"RestartContainer",
skip_all,
@@ -172,7 +183,10 @@ impl Resolve<super::Args> for RestartContainer {
container = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
Ok(
run_komodo_standard_command(
"Docker Restart",
@@ -186,7 +200,7 @@ impl Resolve<super::Args> for RestartContainer {
//
impl Resolve<super::Args> for PauseContainer {
impl Resolve<crate::api::Args> for PauseContainer {
#[instrument(
"PauseContainer",
skip_all,
@@ -196,7 +210,10 @@ impl Resolve<super::Args> for PauseContainer {
container = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
Ok(
run_komodo_standard_command(
"Docker Pause",
@@ -208,7 +225,7 @@ impl Resolve<super::Args> for PauseContainer {
}
}
impl Resolve<super::Args> for UnpauseContainer {
impl Resolve<crate::api::Args> for UnpauseContainer {
#[instrument(
"UnpauseContainer",
skip_all,
@@ -218,7 +235,10 @@ impl Resolve<super::Args> for UnpauseContainer {
container = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
Ok(
run_komodo_standard_command(
"Docker Unpause",
@@ -232,7 +252,7 @@ impl Resolve<super::Args> for UnpauseContainer {
//
impl Resolve<super::Args> for StopContainer {
impl Resolve<crate::api::Args> for StopContainer {
#[instrument(
"StopContainer",
skip_all,
@@ -242,7 +262,10 @@ impl Resolve<super::Args> for StopContainer {
container = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let StopContainer { name, signal, time } = self;
let command = stop_container_command(&name, signal, time);
let log =
@@ -269,7 +292,7 @@ impl Resolve<super::Args> for StopContainer {
//
impl Resolve<super::Args> for RemoveContainer {
impl Resolve<crate::api::Args> for RemoveContainer {
#[instrument(
"RemoveContainer",
skip_all,
@@ -279,7 +302,10 @@ impl Resolve<super::Args> for RemoveContainer {
container = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let RemoveContainer { name, signal, time } = self;
let stop_command = stop_container_command(&name, signal, time);
let command =
@@ -317,7 +343,7 @@ impl Resolve<super::Args> for RemoveContainer {
//
impl Resolve<super::Args> for RenameContainer {
impl Resolve<crate::api::Args> for RenameContainer {
#[instrument(
"RenameContainer",
skip_all,
@@ -328,7 +354,10 @@ impl Resolve<super::Args> for RenameContainer {
new = self.new_name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let RenameContainer {
curr_name,
new_name,
@@ -343,7 +372,7 @@ impl Resolve<super::Args> for RenameContainer {
//
impl Resolve<super::Args> for PruneContainers {
impl Resolve<crate::api::Args> for PruneContainers {
#[instrument(
"PruneContainers",
skip_all,
@@ -352,7 +381,10 @@ impl Resolve<super::Args> for PruneContainers {
core = args.core
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = String::from("docker container prune -f");
Ok(
run_komodo_standard_command("Prune Containers", None, command)
@@ -363,7 +395,7 @@ impl Resolve<super::Args> for PruneContainers {
//
impl Resolve<super::Args> for StartAllContainers {
impl Resolve<crate::api::Args> for StartAllContainers {
#[instrument(
"StartAllContainers",
skip_all,
@@ -374,7 +406,7 @@ impl Resolve<super::Args> for StartAllContainers {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<Vec<Log>> {
let client = docker_client().load();
let client = client
@@ -405,7 +437,7 @@ impl Resolve<super::Args> for StartAllContainers {
//
impl Resolve<super::Args> for RestartAllContainers {
impl Resolve<crate::api::Args> for RestartAllContainers {
#[instrument(
"RestartAllContainers",
skip_all,
@@ -416,7 +448,7 @@ impl Resolve<super::Args> for RestartAllContainers {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<Vec<Log>> {
let client = docker_client().load();
let client = client
@@ -447,7 +479,7 @@ impl Resolve<super::Args> for RestartAllContainers {
//
impl Resolve<super::Args> for PauseAllContainers {
impl Resolve<crate::api::Args> for PauseAllContainers {
#[instrument(
"PauseAllContainers",
skip_all,
@@ -458,7 +490,7 @@ impl Resolve<super::Args> for PauseAllContainers {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<Vec<Log>> {
let client = docker_client().load();
let client = client
@@ -489,7 +521,7 @@ impl Resolve<super::Args> for PauseAllContainers {
//
impl Resolve<super::Args> for UnpauseAllContainers {
impl Resolve<crate::api::Args> for UnpauseAllContainers {
#[instrument(
"UnpauseAllContainers",
skip_all,
@@ -500,7 +532,7 @@ impl Resolve<super::Args> for UnpauseAllContainers {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<Vec<Log>> {
let client = docker_client().load();
let client = client
@@ -531,7 +563,7 @@ impl Resolve<super::Args> for UnpauseAllContainers {
//
impl Resolve<super::Args> for StopAllContainers {
impl Resolve<crate::api::Args> for StopAllContainers {
#[instrument(
"StopAllContainers",
skip_all,
@@ -542,7 +574,7 @@ impl Resolve<super::Args> for StopAllContainers {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<Vec<Log>> {
let client = docker_client().load();
let client = client

View File

@@ -1,34 +1,36 @@
use std::fmt::Write;
use anyhow::Context;
use command::{
KomodoCommandMode, run_komodo_command_with_sanitization,
};
use formatting::format_serror;
use interpolate::Interpolator;
use komodo_client::{
entities::{
EnvironmentVar,
deployment::{
Conversion, Deployment, DeploymentConfig, DeploymentImage,
RestartMode, conversions_from_str, extract_registry_domain,
},
environment_vars_from_str,
update::Log,
use komodo_client::entities::{
deployment::{
Deployment, DeploymentConfig, DeploymentImage, RestartMode,
conversions_from_str, extract_registry_domain,
},
parsers::QUOTE_PATTERN,
environment_vars_from_str,
update::Log,
};
use periphery_client::api::container::{
RemoveContainer, RunContainer,
};
use periphery_client::api::container::{Deploy, RemoveContainer};
use resolver_api::Resolve;
use tracing::Instrument;
use crate::{
config::periphery_config,
docker::{docker_login, pull_image},
helpers::{format_extra_args, format_labels},
helpers::{
push_conversions, push_environment, push_extra_args, push_labels,
},
};
impl Resolve<super::Args> for Deploy {
impl Resolve<crate::api::Args> for RunContainer {
#[instrument(
"Deploy",
"DeployContainer",
skip_all,
fields(
id = args.id.to_string(),
@@ -38,8 +40,11 @@ impl Resolve<super::Args> for Deploy {
stop_time = self.stop_time,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
let Deploy {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let RunContainer {
mut deployment,
stop_signal,
stop_time,
@@ -57,15 +62,15 @@ impl Resolve<super::Args> for Deploy {
{
if image.is_empty() {
return Ok(Log::error(
"get image",
String::from("deployment does not have image attached"),
"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"),
"Get Image",
String::from("Deployment does not have image attached"),
));
};
@@ -77,9 +82,9 @@ impl Resolve<super::Args> for Deploy {
.await
{
return Ok(Log::error(
"docker login",
"Docker Login",
format_serror(
&e.context("failed to login to docker registry").into(),
&e.context("Failed to login to docker registry").into(),
),
));
}
@@ -99,7 +104,7 @@ impl Resolve<super::Args> for Deploy {
let command = docker_run_command(&deployment, image)
.context("Unable to generate valid docker run command")?;
let span = info_span!("RunDockerRun");
let span = info_span!("ExecuteDockerRun");
let Some(log) = run_komodo_command_with_sanitization(
"Docker Run",
None,
@@ -138,75 +143,53 @@ fn docker_run_command(
}: &Deployment,
image: &str,
) -> anyhow::Result<String> {
let ports = parse_conversions(
let mut res =
format!("docker run -d --name {name} --network {network}");
push_conversions(
&mut res,
&conversions_from_str(ports).context("Invalid ports")?,
"-p",
);
let volumes = parse_conversions(
)?;
push_conversions(
&mut res,
&conversions_from_str(volumes).context("Invalid volumes")?,
"-v",
);
let network = parse_network(network);
let restart = parse_restart(restart);
let environment = parse_environment(
)?;
push_environment(
&mut res,
&environment_vars_from_str(environment)
.context("Invalid environment")?,
);
let labels = format_labels(
)?;
push_restart(&mut res, restart)?;
push_labels(
&mut res,
&environment_vars_from_str(labels).context("Invalid labels")?,
);
let command = parse_command(command);
let extra_args = format_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("")
}
push_extra_args(&mut res, extra_args)?;
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::<Vec<_>>()
.join("")
}
write!(&mut res, " {image}")?;
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}")
if !command.is_empty() {
write!(&mut res, " {command}")?;
}
Ok(res)
}
fn push_restart(
command: &mut String,
restart: &RestartMode,
) -> anyhow::Result<()> {
let restart = match restart {
RestartMode::OnFailure => "on-failure:10",
_ => restart.as_ref(),
};
write!(command, " --restart {restart}")
.context("Failed to write restart mode")
}

View File

@@ -22,8 +22,11 @@ use crate::{docker::docker_login, state::docker_client};
// IMAGE
// =====
impl Resolve<super::Args> for InspectImage {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Image> {
impl Resolve<crate::api::Args> for InspectImage {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Image> {
let client = docker_client().load();
let client = client
.iter()
@@ -35,10 +38,10 @@ impl Resolve<super::Args> for InspectImage {
//
impl Resolve<super::Args> for ImageHistory {
impl Resolve<crate::api::Args> for ImageHistory {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<Vec<ImageHistoryResponseItem>> {
let client = docker_client().load();
let client = client
@@ -60,7 +63,7 @@ fn pull_cache() -> &'static TimeoutCache<String, Log> {
PULL_CACHE.get_or_init(Default::default)
}
impl Resolve<super::Args> for PullImage {
impl Resolve<crate::api::Args> for PullImage {
#[instrument(
"PullImage",
skip_all,
@@ -71,7 +74,10 @@ impl Resolve<super::Args> for PullImage {
account = self.account,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let PullImage {
name,
account,
@@ -118,7 +124,7 @@ impl Resolve<super::Args> for PullImage {
//
impl Resolve<super::Args> for DeleteImage {
impl Resolve<crate::api::Args> for DeleteImage {
#[instrument(
"DeleteImage",
skip_all,
@@ -128,7 +134,10 @@ impl Resolve<super::Args> for DeleteImage {
image = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = format!("docker image rm {}", self.name);
Ok(
run_komodo_standard_command("Delete Image", None, command)
@@ -139,7 +148,7 @@ impl Resolve<super::Args> for DeleteImage {
//
impl Resolve<super::Args> for PruneImages {
impl Resolve<crate::api::Args> for PruneImages {
#[instrument(
"PruneImages",
skip_all,
@@ -148,7 +157,10 @@ impl Resolve<super::Args> for PruneImages {
core = args.core,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = String::from("docker image prune -a -f");
Ok(
run_komodo_standard_command("Prune Images", None, command)
@@ -161,8 +173,11 @@ impl Resolve<super::Args> for PruneImages {
// NETWORK
// =======
impl Resolve<super::Args> for InspectNetwork {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Network> {
impl Resolve<crate::api::Args> for InspectNetwork {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Network> {
let client = docker_client().load();
let client = client
.iter()
@@ -174,7 +189,7 @@ impl Resolve<super::Args> for InspectNetwork {
//
impl Resolve<super::Args> for CreateNetwork {
impl Resolve<crate::api::Args> for CreateNetwork {
#[instrument(
"CreateNetwork",
skip_all,
@@ -185,7 +200,10 @@ impl Resolve<super::Args> for CreateNetwork {
driver = self.driver,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let CreateNetwork { name, driver } = self;
let driver = match driver {
Some(driver) => format!(" -d {driver}"),
@@ -201,7 +219,7 @@ impl Resolve<super::Args> for CreateNetwork {
//
impl Resolve<super::Args> for DeleteNetwork {
impl Resolve<crate::api::Args> for DeleteNetwork {
#[instrument(
"DeleteNetwork",
skip_all,
@@ -211,7 +229,10 @@ impl Resolve<super::Args> for DeleteNetwork {
network = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = format!("docker network rm {}", self.name);
Ok(
run_komodo_standard_command("Delete Network", None, command)
@@ -222,7 +243,7 @@ impl Resolve<super::Args> for DeleteNetwork {
//
impl Resolve<super::Args> for PruneNetworks {
impl Resolve<crate::api::Args> for PruneNetworks {
#[instrument(
"PruneNetworks",
skip_all,
@@ -231,7 +252,10 @@ impl Resolve<super::Args> for PruneNetworks {
core = args.core,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = String::from("docker network prune -f");
Ok(
run_komodo_standard_command("Prune Networks", None, command)
@@ -244,8 +268,11 @@ impl Resolve<super::Args> for PruneNetworks {
// VOLUME
// ======
impl Resolve<super::Args> for InspectVolume {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Volume> {
impl Resolve<crate::api::Args> for InspectVolume {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Volume> {
let client = docker_client().load();
let client = client
.iter()
@@ -257,7 +284,7 @@ impl Resolve<super::Args> for InspectVolume {
//
impl Resolve<super::Args> for DeleteVolume {
impl Resolve<crate::api::Args> for DeleteVolume {
#[instrument(
"DeleteVolume",
skip_all,
@@ -267,7 +294,10 @@ impl Resolve<super::Args> for DeleteVolume {
volume = self.name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = format!("docker volume rm {}", self.name);
Ok(
run_komodo_standard_command("Delete Volume", None, command)
@@ -278,7 +308,7 @@ impl Resolve<super::Args> for DeleteVolume {
//
impl Resolve<super::Args> for PruneVolumes {
impl Resolve<crate::api::Args> for PruneVolumes {
#[instrument(
"PruneVolumes",
skip_all,
@@ -287,7 +317,10 @@ impl Resolve<super::Args> for PruneVolumes {
core = args.core,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let command = String::from("docker volume prune -a -f");
Ok(
run_komodo_standard_command("Prune Volumes", None, command)

View File

@@ -16,10 +16,10 @@ use crate::{
config::periphery_config, helpers::handle_post_repo_execution,
};
impl Resolve<super::Args> for GetLatestCommit {
impl Resolve<crate::api::Args> for GetLatestCommit {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<Option<LatestCommit>> {
let repo_path = match self.path {
Some(p) => PathBuf::from(p),
@@ -33,7 +33,7 @@ impl Resolve<super::Args> for GetLatestCommit {
}
}
impl Resolve<super::Args> for CloneRepo {
impl Resolve<crate::api::Args> for CloneRepo {
#[instrument(
"CloneRepo",
skip_all,
@@ -46,7 +46,7 @@ impl Resolve<super::Args> for CloneRepo {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<PeripheryRepoExecutionResponse> {
let CloneRepo {
args,
@@ -79,7 +79,7 @@ impl Resolve<super::Args> for CloneRepo {
//
impl Resolve<super::Args> for PullRepo {
impl Resolve<crate::api::Args> for PullRepo {
#[instrument(
"PullRepo",
skip_all,
@@ -92,7 +92,7 @@ impl Resolve<super::Args> for PullRepo {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<PeripheryRepoExecutionResponse> {
let PullRepo {
args,
@@ -124,7 +124,7 @@ impl Resolve<super::Args> for PullRepo {
//
impl Resolve<super::Args> for PullOrCloneRepo {
impl Resolve<crate::api::Args> for PullOrCloneRepo {
#[instrument(
"PullOrCloneRepo",
skip_all,
@@ -137,7 +137,7 @@ impl Resolve<super::Args> for PullOrCloneRepo {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<PeripheryRepoExecutionResponse> {
let PullOrCloneRepo {
args,
@@ -171,7 +171,7 @@ impl Resolve<super::Args> for PullOrCloneRepo {
//
impl Resolve<super::Args> for RenameRepo {
impl Resolve<crate::api::Args> for RenameRepo {
#[instrument(
"RenameRepo",
skip_all,
@@ -182,7 +182,10 @@ impl Resolve<super::Args> for RenameRepo {
new_name = self.new_name,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let RenameRepo {
curr_name,
new_name,
@@ -201,7 +204,7 @@ impl Resolve<super::Args> for RenameRepo {
//
impl Resolve<super::Args> for DeleteRepo {
impl Resolve<crate::api::Args> for DeleteRepo {
#[instrument(
"DeleteRepo",
skip_all,
@@ -212,7 +215,10 @@ impl Resolve<super::Args> for DeleteRepo {
is_build = self.is_build,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Log> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let DeleteRepo { name, is_build } = self;
// If using custom clone path, it will be passed by core instead of name.
// So the join will resolve to just the absolute path.

View File

@@ -12,7 +12,7 @@ use crate::{
//
impl Resolve<super::Args> for RotatePrivateKey {
impl Resolve<crate::api::Args> for RotatePrivateKey {
#[instrument(
"RotatePrivateKey",
skip_all,
@@ -23,7 +23,7 @@ impl Resolve<super::Args> for RotatePrivateKey {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<RotatePrivateKeyResponse> {
let public_key = periphery_keys().rotate().await?.into_inner();
info!("New Public Key: {public_key}");
@@ -33,7 +33,7 @@ impl Resolve<super::Args> for RotatePrivateKey {
//
impl Resolve<super::Args> for RotateCorePublicKey {
impl Resolve<crate::api::Args> for RotateCorePublicKey {
#[instrument(
"RotateCorePublicKey",
skip_all,
@@ -45,7 +45,7 @@ impl Resolve<super::Args> for RotateCorePublicKey {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<NoData> {
let config = periphery_config();

View File

@@ -21,7 +21,6 @@ pub mod terminal;
mod build;
mod compose;
mod container;
mod deploy;
mod docker;
mod git;
mod keys;
@@ -94,7 +93,7 @@ pub enum PeripheryRequest {
GetFullContainerStats(GetFullContainerStats),
// Container (Write)
Deploy(Deploy),
RunContainer(RunContainer),
StartContainer(StartContainer),
RestartContainer(RestartContainer),
PauseContainer(PauseContainer),
@@ -136,21 +135,25 @@ pub enum PeripheryRequest {
// All in one (Write)
PruneSystem(PruneSystem),
// Swarm
// Swarm (Read)
PollSwarmStatus(PollSwarmStatus),
InspectSwarmNode(InspectSwarmNode),
UpdateSwarmNode(UpdateSwarmNode),
RemoveSwarmNodes(RemoveSwarmNodes),
InspectSwarmStack(InspectSwarmStack),
RemoveSwarmStacks(RemoveSwarmStacks),
InspectSwarmService(InspectSwarmService),
GetSwarmServiceLog(GetSwarmServiceLog),
GetSwarmServiceLogSearch(GetSwarmServiceLogSearch),
RemoveSwarmServices(RemoveSwarmServices),
InspectSwarmTask(InspectSwarmTask),
InspectSwarmConfig(InspectSwarmConfig),
RemoveSwarmConfigs(RemoveSwarmConfigs),
InspectSwarmSecret(InspectSwarmSecret),
// Swarm (Write)
UpdateSwarmNode(UpdateSwarmNode),
RemoveSwarmNodes(RemoveSwarmNodes),
DeploySwarmStack(DeploySwarmStack),
RemoveSwarmStacks(RemoveSwarmStacks),
CreateSwarmService(CreateSwarmService),
RemoveSwarmServices(RemoveSwarmServices),
RemoveSwarmConfigs(RemoveSwarmConfigs),
RemoveSwarmSecrets(RemoveSwarmSecrets),
// Terminal

View File

@@ -13,10 +13,10 @@ use crate::{
},
};
impl Resolve<super::Args> for PollStatus {
impl Resolve<crate::api::Args> for PollStatus {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<PollStatusResponse> {
let stats_client = stats_client().read().await;

View File

@@ -1,384 +0,0 @@
use anyhow::Context as _;
use command::{
run_komodo_shell_command, run_komodo_standard_command,
};
use komodo_client::entities::{
docker::{
SwarmLists, config::SwarmConfig, node::SwarmNode,
secret::SwarmSecret, service::SwarmService, stack::SwarmStack,
task::SwarmTask,
},
update::Log,
};
use periphery_client::api::swarm::*;
use resolver_api::Resolve;
use crate::{
docker::{
config::{inspect_swarm_config, list_swarm_configs},
stack::{inspect_swarm_stack, list_swarm_stacks},
},
helpers::format_log_grep,
state::docker_client,
};
impl Resolve<super::Args> for PollSwarmStatus {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<PollSwarmStatusResponse> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
let (inspect, nodes, services, tasks, secrets, configs, stacks) = tokio::join!(
client.inspect_swarm(),
client.list_swarm_nodes(),
client.list_swarm_services(),
client.list_swarm_tasks(),
client.list_swarm_secrets(),
list_swarm_configs(),
list_swarm_stacks(),
);
Ok(PollSwarmStatusResponse {
inspect: inspect.ok(),
lists: SwarmLists {
nodes: nodes.unwrap_or_default(),
services: services.unwrap_or_default(),
tasks: tasks.unwrap_or_default(),
secrets: secrets.unwrap_or_default(),
configs: configs.unwrap_or_default(),
stacks: stacks.unwrap_or_default(),
},
})
}
}
// ======
// Node
// ======
impl Resolve<super::Args> for InspectSwarmNode {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmNode> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_node(&self.node).await
}
}
impl Resolve<super::Args> for UpdateSwarmNode {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let mut command = String::from("docker node update");
if let Some(role) = self.role {
command += " --role=";
command += role.as_ref();
}
if let Some(availability) = self.availability {
command += " --availability=";
command += availability.as_ref();
}
if let Some(label_add) = self.label_add {
for (key, value) in label_add {
command += " --label-add ";
command += &key;
if let Some(value) = value {
command += "=";
command += &value;
}
}
}
if let Some(label_rm) = self.label_rm {
for key in label_rm {
command += " --label-rm ";
command += &key;
}
}
command += " ";
command += &self.node;
Ok(
run_komodo_standard_command("Update Swarm Node", None, command)
.await,
)
}
}
impl Resolve<super::Args> for RemoveSwarmNodes {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let mut command = String::from("docker node rm");
if self.force {
command += " --force"
}
for node in self.nodes {
command += " ";
command += &node;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Nodes",
None,
command,
)
.await,
)
}
}
// =======
// Stack
// =======
impl Resolve<super::Args> for InspectSwarmStack {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmStack> {
inspect_swarm_stack(self.stack).await
}
}
impl Resolve<super::Args> for RemoveSwarmStacks {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let mut command = String::from("docker stack rm");
// This defaults to true, only need when false
if !self.detach {
command += " --detach=false"
}
for stack in self.stacks {
command += " ";
command += &stack;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Stacks",
None,
command,
)
.await,
)
}
}
// =========
// Service
// =========
impl Resolve<super::Args> for InspectSwarmService {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmService> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_service(&self.service).await
}
}
impl Resolve<super::Args> for GetSwarmServiceLog {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let GetSwarmServiceLog {
service,
tail,
timestamps,
no_task_ids,
no_resolve,
details,
} = self;
let timestamps = if timestamps {
" --timestamps"
} else {
Default::default()
};
let no_task_ids = if no_task_ids {
" --no-task-ids"
} else {
Default::default()
};
let no_resolve = if no_resolve {
" --no-resolve"
} else {
Default::default()
};
let details = if details {
" --details"
} else {
Default::default()
};
let command = format!(
"docker service logs --tail {tail}{timestamps}{no_task_ids}{no_resolve}{details} {service}",
);
Ok(
run_komodo_standard_command(
"Get Swarm Service Log",
None,
command,
)
.await,
)
}
}
impl Resolve<super::Args> for GetSwarmServiceLogSearch {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let GetSwarmServiceLogSearch {
service,
terms,
combinator,
invert,
timestamps,
no_task_ids,
no_resolve,
details,
} = self;
let timestamps = if timestamps {
" --timestamps"
} else {
Default::default()
};
let no_task_ids = if no_task_ids {
" --no-task-ids"
} else {
Default::default()
};
let no_resolve = if no_resolve {
" --no-resolve"
} else {
Default::default()
};
let details = if details {
" --details"
} else {
Default::default()
};
let grep = format_log_grep(&terms, combinator, invert);
let command = format!(
"docker service logs --tail 5000{timestamps}{no_task_ids}{no_resolve}{details} {service} 2>&1 | {grep}",
);
Ok(
run_komodo_shell_command(
"Search Swarm Service Log",
None,
command,
)
.await,
)
}
}
impl Resolve<super::Args> for RemoveSwarmServices {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let mut command = String::from("docker service rm");
for service in self.services {
command += " ";
command += &service;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Services",
None,
command,
)
.await,
)
}
}
// ======
// Task
// ======
impl Resolve<super::Args> for InspectSwarmTask {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmTask> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_task(&self.task).await
}
}
// ========
// Config
// ========
impl Resolve<super::Args> for InspectSwarmConfig {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<Vec<SwarmConfig>> {
inspect_swarm_config(&self.config).await
}
}
impl Resolve<super::Args> for RemoveSwarmConfigs {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let mut command = String::from("docker config rm");
for config in self.configs {
command += " ";
command += &config;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Configs",
None,
command,
)
.await,
)
}
}
// ========
// Secret
// ========
impl Resolve<super::Args> for InspectSwarmSecret {
async fn resolve(
self,
_: &super::Args,
) -> anyhow::Result<SwarmSecret> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_secret(&self.secret).await
}
}
impl Resolve<super::Args> for RemoveSwarmSecrets {
async fn resolve(self, _: &super::Args) -> anyhow::Result<Log> {
let mut command = String::from("docker secret rm");
for secret in self.secrets {
command += " ";
command += &secret;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Secrets",
None,
command,
)
.await,
)
}
}

View File

@@ -0,0 +1,271 @@
use anyhow::Context as _;
use command::run_komodo_standard_command;
use komodo_client::entities::{
docker::{
SwarmLists, config::SwarmConfig, node::SwarmNode,
secret::SwarmSecret, task::SwarmTask,
},
update::Log,
};
use periphery_client::api::swarm::*;
use resolver_api::Resolve;
use crate::{
docker::{
config::{inspect_swarm_config, list_swarm_configs},
stack::list_swarm_stacks,
},
state::docker_client,
};
mod service;
mod stack;
impl Resolve<crate::api::Args> for PollSwarmStatus {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<PollSwarmStatusResponse> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
let (inspect, nodes, services, tasks, secrets, configs, stacks) = tokio::join!(
client.inspect_swarm(),
client.list_swarm_nodes(),
client.list_swarm_services(),
client.list_swarm_tasks(),
client.list_swarm_secrets(),
list_swarm_configs(),
list_swarm_stacks(),
);
Ok(PollSwarmStatusResponse {
inspect: inspect.ok(),
lists: SwarmLists {
nodes: nodes.unwrap_or_default(),
services: services.unwrap_or_default(),
tasks: tasks.unwrap_or_default(),
secrets: secrets.unwrap_or_default(),
configs: configs.unwrap_or_default(),
stacks: stacks.unwrap_or_default(),
},
})
}
}
// ======
// Node
// ======
impl Resolve<crate::api::Args> for InspectSwarmNode {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<SwarmNode> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_node(&self.node).await
}
}
impl Resolve<crate::api::Args> for UpdateSwarmNode {
#[instrument(
"UpdateSwarmNode",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
node = self.node,
update = serde_json::to_string(&self).unwrap_or_else(|e| e.to_string())
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let mut command = String::from("docker node update");
if let Some(role) = self.role {
command += " --role=";
command += role.as_ref();
}
if let Some(availability) = self.availability {
command += " --availability=";
command += availability.as_ref();
}
if let Some(label_add) = self.label_add {
for (key, value) in label_add {
command += " --label-add ";
command += &key;
if let Some(value) = value {
command += "=";
command += &value;
}
}
}
if let Some(label_rm) = self.label_rm {
for key in label_rm {
command += " --label-rm ";
command += &key;
}
}
command += " ";
command += &self.node;
Ok(
run_komodo_standard_command("Update Swarm Node", None, command)
.await,
)
}
}
impl Resolve<crate::api::Args> for RemoveSwarmNodes {
#[instrument(
"RemoveSwarmNodes",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
nodes = serde_json::to_string(&self.nodes).unwrap_or_else(|e| e.to_string()),
force = self.force,
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let mut command = String::from("docker node rm");
if self.force {
command += " --force"
}
for node in self.nodes {
command += " ";
command += &node;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Nodes",
None,
command,
)
.await,
)
}
}
// ======
// Task
// ======
impl Resolve<crate::api::Args> for InspectSwarmTask {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<SwarmTask> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_task(&self.task).await
}
}
// ========
// Config
// ========
impl Resolve<crate::api::Args> for InspectSwarmConfig {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Vec<SwarmConfig>> {
inspect_swarm_config(&self.config).await
}
}
impl Resolve<crate::api::Args> for RemoveSwarmConfigs {
#[instrument(
"RemoveSwarmConfigs",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
configs = serde_json::to_string(&self.configs).unwrap_or_else(|e| e.to_string()),
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let mut command = String::from("docker config rm");
for config in self.configs {
command += " ";
command += &config;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Configs",
None,
command,
)
.await,
)
}
}
// ========
// Secret
// ========
impl Resolve<crate::api::Args> for InspectSwarmSecret {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<SwarmSecret> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_secret(&self.secret).await
}
}
impl Resolve<crate::api::Args> for RemoveSwarmSecrets {
#[instrument(
"RemoveSwarmSecrets",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
secrets = serde_json::to_string(&self.secrets).unwrap_or_else(|e| e.to_string()),
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let mut command = String::from("docker secret rm");
for secret in self.secrets {
command += " ";
command += &secret;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Secrets",
None,
command,
)
.await,
)
}
}

View File

@@ -0,0 +1,327 @@
use std::fmt::Write;
use anyhow::Context as _;
use command::{
KomodoCommandMode, run_komodo_command_with_sanitization,
run_komodo_shell_command, run_komodo_standard_command,
};
use formatting::format_serror;
use interpolate::Interpolator;
use komodo_client::entities::{
deployment::{
Deployment, DeploymentConfig, DeploymentImage,
conversions_from_str, extract_registry_domain,
},
docker::service::SwarmService,
environment_vars_from_str,
update::Log,
};
use periphery_client::api::swarm::{
CreateSwarmService, GetSwarmServiceLog, GetSwarmServiceLogSearch,
InspectSwarmService, RemoveSwarmServices,
};
use resolver_api::Resolve;
use tracing::Instrument;
use crate::{
config::periphery_config,
docker::docker_login,
helpers::{
format_log_grep, push_conversions, push_environment,
push_extra_args, push_labels,
},
state::docker_client,
};
impl Resolve<crate::api::Args> for InspectSwarmService {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<SwarmService> {
let client = docker_client().load();
let client = client
.iter()
.next()
.context("Could not connect to docker client")?;
client.inspect_swarm_service(&self.service).await
}
}
impl Resolve<crate::api::Args> for GetSwarmServiceLog {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Log> {
let GetSwarmServiceLog {
service,
tail,
timestamps,
no_task_ids,
no_resolve,
details,
} = self;
let timestamps = if timestamps {
" --timestamps"
} else {
Default::default()
};
let no_task_ids = if no_task_ids {
" --no-task-ids"
} else {
Default::default()
};
let no_resolve = if no_resolve {
" --no-resolve"
} else {
Default::default()
};
let details = if details {
" --details"
} else {
Default::default()
};
let command = format!(
"docker service logs --tail {tail}{timestamps}{no_task_ids}{no_resolve}{details} {service}",
);
Ok(
run_komodo_standard_command(
"Get Swarm Service Log",
None,
command,
)
.await,
)
}
}
impl Resolve<crate::api::Args> for GetSwarmServiceLogSearch {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<Log> {
let GetSwarmServiceLogSearch {
service,
terms,
combinator,
invert,
timestamps,
no_task_ids,
no_resolve,
details,
} = self;
let timestamps = if timestamps {
" --timestamps"
} else {
Default::default()
};
let no_task_ids = if no_task_ids {
" --no-task-ids"
} else {
Default::default()
};
let no_resolve = if no_resolve {
" --no-resolve"
} else {
Default::default()
};
let details = if details {
" --details"
} else {
Default::default()
};
let grep = format_log_grep(&terms, combinator, invert);
let command = format!(
"docker service logs --tail 5000{timestamps}{no_task_ids}{no_resolve}{details} {service} 2>&1 | {grep}",
);
Ok(
run_komodo_shell_command(
"Search Swarm Service Log",
None,
command,
)
.await,
)
}
}
impl Resolve<crate::api::Args> for RemoveSwarmServices {
#[instrument(
"RemoveSwarmServices",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
services = serde_json::to_string(&self.services).unwrap_or_else(|e| e.to_string()),
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let mut command = String::from("docker service rm");
for service in self.services {
command += " ";
command += &service;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Services",
None,
command,
)
.await,
)
}
}
impl Resolve<crate::api::Args> for CreateSwarmService {
#[instrument(
"CreateSwarmService",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
deployment = &self.deployment.name,
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> Result<Self::Response, Self::Error> {
let CreateSwarmService {
mut deployment,
registry_token,
mut replacers,
} = self;
let mut interpolator =
Interpolator::new(None, &periphery_config().secrets);
interpolator.interpolate_deployment(&mut deployment)?;
replacers.extend(interpolator.secret_replacers);
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 build replaced by Core",
),
));
};
let use_with_registry_auth = match docker_login(
&extract_registry_domain(image)?,
&deployment.config.image_registry_account,
registry_token.as_deref(),
)
.await
{
Ok(res) => res,
Err(e) => {
return Ok(Log::error(
"Docker Login",
format_serror(
&e.context("Failed to login to docker registry").into(),
),
));
}
};
let command = docker_service_create_command(
&deployment,
image,
use_with_registry_auth,
)
.context(
"Unable to generate valid docker service create command",
)?;
let span = info_span!("ExecuteDockerServiceCreate");
let Some(log) = run_komodo_command_with_sanitization(
"Docker Service Create",
None,
command,
KomodoCommandMode::Shell,
&replacers,
)
.instrument(span)
.await
else {
// The none case is only for empty command,
// this won't be the case given it is populated above.
unreachable!()
};
Ok(log)
}
}
fn docker_service_create_command(
Deployment {
name,
config:
DeploymentConfig {
volumes,
ports,
network,
command,
environment,
labels,
extra_args,
..
},
..
}: &Deployment,
image: &str,
use_with_registry_auth: bool,
) -> anyhow::Result<String> {
let mut res = format!(
"docker service create --name {name} --network {network}"
);
push_conversions(
&mut res,
&conversions_from_str(ports).context("Invalid ports")?,
"-p",
)?;
push_conversions(
&mut res,
&conversions_from_str(volumes).context("Invalid volumes")?,
"--mount",
)?;
push_environment(
&mut res,
&environment_vars_from_str(environment)
.context("Invalid environment")?,
)?;
push_labels(
&mut res,
&environment_vars_from_str(labels).context("Invalid labels")?,
)?;
if use_with_registry_auth {
res += " --with-registry-auth";
}
push_extra_args(&mut res, extra_args)?;
write!(&mut res, " {image}")?;
if !command.is_empty() {
write!(&mut res, " {command}")?;
}
Ok(res)
}

View File

@@ -0,0 +1,72 @@
use command::run_komodo_standard_command;
use komodo_client::entities::{
docker::stack::SwarmStack, update::Log,
};
use periphery_client::api::swarm::{
DeploySwarmStack, InspectSwarmStack, RemoveSwarmStacks,
};
use resolver_api::Resolve;
use crate::docker::stack::inspect_swarm_stack;
impl Resolve<crate::api::Args> for InspectSwarmStack {
async fn resolve(
self,
_: &crate::api::Args,
) -> anyhow::Result<SwarmStack> {
inspect_swarm_stack(self.stack).await
}
}
impl Resolve<crate::api::Args> for RemoveSwarmStacks {
#[instrument(
"RemoveSwarmStacks",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
stacks = serde_json::to_string(&self.stacks).unwrap_or_else(|e| e.to_string()),
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Log> {
let mut command = String::from("docker stack rm");
// This defaults to true, only need when false
if !self.detach {
command += " --detach=false"
}
for stack in self.stacks {
command += " ";
command += &stack;
}
Ok(
run_komodo_standard_command(
"Remove Swarm Stacks",
None,
command,
)
.await,
)
}
}
impl Resolve<crate::api::Args> for DeploySwarmStack {
#[instrument(
"DeploySwarmStack",
skip_all,
fields(
id = args.id.to_string(),
core = args.core,
stack = self.stack.name,
repo = self.repo.as_ref().map(|repo| &repo.name),
)
)]
async fn resolve(
self,
args: &crate::api::Args,
) -> Result<Self::Response, Self::Error> {
todo!()
}
}

View File

@@ -25,10 +25,10 @@ use crate::{
//
impl Resolve<super::Args> for ListTerminals {
impl Resolve<crate::api::Args> for ListTerminals {
async fn resolve(
self,
_: &super::Args,
_: &crate::api::Args,
) -> anyhow::Result<Vec<Terminal>> {
clean_up_terminals().await;
Ok(list_terminals(self.target.as_ref()).await)
@@ -37,7 +37,7 @@ impl Resolve<super::Args> for ListTerminals {
//
impl Resolve<super::Args> for CreateServerTerminal {
impl Resolve<crate::api::Args> for CreateServerTerminal {
#[instrument(
"CreateServerTerminal",
skip_all,
@@ -51,7 +51,7 @@ impl Resolve<super::Args> for CreateServerTerminal {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<NoData> {
if periphery_config().disable_terminals {
return Err(anyhow!(
@@ -71,7 +71,7 @@ impl Resolve<super::Args> for CreateServerTerminal {
//
impl Resolve<super::Args> for CreateContainerExecTerminal {
impl Resolve<crate::api::Args> for CreateContainerExecTerminal {
#[instrument(
"CreateContainerExecTerminal",
skip_all,
@@ -86,7 +86,7 @@ impl Resolve<super::Args> for CreateContainerExecTerminal {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<NoData> {
if periphery_config().disable_container_terminals {
return Err(anyhow!(
@@ -119,7 +119,7 @@ impl Resolve<super::Args> for CreateContainerExecTerminal {
//
impl Resolve<super::Args> for CreateContainerAttachTerminal {
impl Resolve<crate::api::Args> for CreateContainerAttachTerminal {
#[instrument(
"CreateContainerAttachTerminal",
skip_all,
@@ -133,7 +133,7 @@ impl Resolve<super::Args> for CreateContainerAttachTerminal {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<NoData> {
if periphery_config().disable_container_terminals {
return Err(anyhow!(
@@ -164,7 +164,7 @@ impl Resolve<super::Args> for CreateContainerAttachTerminal {
//
impl Resolve<super::Args> for DeleteTerminal {
impl Resolve<crate::api::Args> for DeleteTerminal {
#[instrument(
"DeleteTerminal",
skip_all,
@@ -176,7 +176,7 @@ impl Resolve<super::Args> for DeleteTerminal {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<NoData> {
delete_terminal(&self.target, &self.terminal).await;
Ok(NoData {})
@@ -185,7 +185,7 @@ impl Resolve<super::Args> for DeleteTerminal {
//
impl Resolve<super::Args> for DeleteAllTerminals {
impl Resolve<crate::api::Args> for DeleteAllTerminals {
#[instrument(
"DeleteAllTerminals",
skip_all,
@@ -196,7 +196,7 @@ impl Resolve<super::Args> for DeleteAllTerminals {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<NoData> {
delete_all_terminals().await;
Ok(NoData {})
@@ -205,7 +205,7 @@ impl Resolve<super::Args> for DeleteAllTerminals {
//
impl Resolve<super::Args> for ConnectTerminal {
impl Resolve<crate::api::Args> for ConnectTerminal {
#[instrument(
"ConnectTerminal",
skip_all,
@@ -215,7 +215,10 @@ impl Resolve<super::Args> for ConnectTerminal {
terminal = self.terminal,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Uuid> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Uuid> {
let connection =
core_connections().get(&args.core).await.with_context(
|| format!("Failed to find channel for {}", args.core),
@@ -234,7 +237,7 @@ impl Resolve<super::Args> for ConnectTerminal {
//
impl Resolve<super::Args> for DisconnectTerminal {
impl Resolve<crate::api::Args> for DisconnectTerminal {
#[instrument(
"DisconnectTerminal",
skip_all,
@@ -246,7 +249,7 @@ impl Resolve<super::Args> for DisconnectTerminal {
)]
async fn resolve(
self,
args: &super::Args,
args: &crate::api::Args,
) -> anyhow::Result<NoData> {
terminal_channels().remove(&self.channel).await;
Ok(NoData {})
@@ -255,7 +258,7 @@ impl Resolve<super::Args> for DisconnectTerminal {
//
impl Resolve<super::Args> for ExecuteTerminal {
impl Resolve<crate::api::Args> for ExecuteTerminal {
#[instrument(
"ExecuteTerminal",
skip_all,
@@ -266,7 +269,10 @@ impl Resolve<super::Args> for ExecuteTerminal {
command = self.command,
)
)]
async fn resolve(self, args: &super::Args) -> anyhow::Result<Uuid> {
async fn resolve(
self,
args: &crate::api::Args,
) -> anyhow::Result<Uuid> {
let channel =
core_connections().get(&args.core).await.with_context(
|| format!("Failed to find channel for {}", args.core),

View File

@@ -34,7 +34,7 @@ impl DockerClient {
}
}
/// Returns whether build result should be pushed after build
/// Returns whether login was actually performed.
#[instrument("DockerLogin", skip(registry_token))]
pub async fn docker_login(
domain: &str,
@@ -45,30 +45,33 @@ pub async fn docker_login(
if domain.is_empty() || account.is_empty() {
return Ok(false);
}
let registry_token = match registry_token {
Some(token) => token,
None => crate::helpers::registry_token(domain, account)?,
};
let log = run_shell_command(&format!(
"echo {registry_token} | docker login {domain} --username '{account}' --password-stdin",
), None)
.await;
if log.success() {
Ok(true)
} else {
let mut e = anyhow!("End of trace");
for line in
log.stderr.split('\n').filter(|line| !line.is_empty()).rev()
{
e = e.context(line.to_string());
}
for line in
log.stdout.split('\n').filter(|line| !line.is_empty()).rev()
{
e = e.context(line.to_string());
}
Err(e.context(format!("Registry {domain} login error")))
return Ok(true);
}
let mut e = anyhow!("End of trace");
for line in
log.stderr.split('\n').filter(|line| !line.is_empty()).rev()
{
e = e.context(line.to_string());
}
for line in
log.stdout.split('\n').filter(|line| !line.is_empty()).rev()
{
e = e.context(line.to_string());
}
Err(e.context(format!("Registry {domain} login error")))
}
#[instrument("PullImage")]

View File

@@ -1,6 +1,6 @@
use std::{
net::IpAddr, path::PathBuf, str::FromStr as _, sync::OnceLock,
time::Duration,
fmt::Write, net::IpAddr, path::PathBuf, str::FromStr as _,
sync::OnceLock, time::Duration,
};
use anyhow::Context;
@@ -14,6 +14,7 @@ use komodo_client::{
entities::{
EnvironmentVar, RepoExecutionArgs, RepoExecutionResponse,
SearchCombinator, SystemCommand, all_logs_success,
deployment::Conversion,
},
parsers::QUOTE_PATTERN,
};
@@ -34,6 +35,17 @@ pub fn format_extra_args(extra_args: &[String]) -> String {
}
}
pub fn push_extra_args(
command: &mut String,
extra_args: &[String],
) -> anyhow::Result<()> {
for arg in extra_args {
write!(command, " {arg}")
.context("Failed to write extra args to command")?
}
Ok(())
}
pub fn format_labels(labels: &[EnvironmentVar]) -> String {
labels
.iter()
@@ -51,6 +63,56 @@ pub fn format_labels(labels: &[EnvironmentVar]) -> String {
.join("")
}
pub fn push_labels(
command: &mut String,
labels: &[EnvironmentVar],
) -> anyhow::Result<()> {
for label in labels {
if label.value.starts_with(QUOTE_PATTERN)
&& label.value.ends_with(QUOTE_PATTERN)
{
write!(command, " --label {}={}", label.variable, label.value)
} else {
write!(
command,
" --label {}=\"{}\"",
label.variable, label.value
)
}
.context("Failed to write labels to command")?;
}
Ok(())
}
pub fn push_conversions(
command: &mut String,
conversions: &[Conversion],
flag: &str,
) -> anyhow::Result<()> {
for Conversion { local, container } in conversions {
write!(command, " {flag} {local}:{container}")
.context("Failed to format conversions")?;
}
Ok(())
}
pub fn push_environment(
command: &mut String,
environment: &[EnvironmentVar],
) -> anyhow::Result<()> {
for EnvironmentVar { variable, value } in environment {
if value.starts_with(QUOTE_PATTERN)
&& value.ends_with(QUOTE_PATTERN)
{
write!(command, " --env {variable}={value}")
} else {
write!(command, " --env {variable}=\"{value}\"")
}
.context("Failed to format environment")?;
}
Ok(())
}
pub fn format_log_grep(
terms: &[String],
combinator: SearchCombinator,

View File

@@ -5,7 +5,7 @@ use derive_default_builder::DefaultBuilder;
use derive_variants::EnumVariants;
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString};
use strum::{AsRefStr, Display, EnumString};
use typeshare::typeshare;
use crate::{
@@ -58,7 +58,19 @@ pub type _PartialDeploymentConfig = PartialDeploymentConfig;
#[partial_derive(Serialize, Deserialize, Debug, Clone, Default)]
#[partial(skip_serializing_none, from, diff)]
pub struct DeploymentConfig {
/// The id of server the deployment is deployed on.
/// The Swarm to deploy the Deployment on (as a Swarm Service), setting the Deployment into Swarm mode.
///
/// Note. If both swarm_id and server_id are set,
/// swarm_id overrides server_id and the Deployment will be in Swarm mode.
#[serde(default, alias = "swarm")]
#[partial_attr(serde(alias = "swarm"))]
#[builder(default)]
pub swarm_id: String,
/// The Server to deploy the Deployment on, setting the Deployment into Container mode.
///
/// Note. If both swarm_id and server_id are set,
/// swarm_id overrides server_id and the Deployment will be in Swarm mode.
#[serde(default, alias = "server")]
#[partial_attr(serde(alias = "server"))]
#[builder(default)]
@@ -134,6 +146,14 @@ pub struct DeploymentConfig {
#[builder(default)]
pub command: String,
/// The number of replicas for the Service.
///
/// Note. Only used in Swarm mode.
#[serde(default = "default_replicas")]
#[builder(default = "default_replicas()")]
#[partial_default(default_replicas())]
pub replicas: i32,
/// The default termination signal to use to stop the deployment. Defaults to SigTerm (default docker signal).
#[serde(default)]
#[builder(default)]
@@ -145,7 +165,8 @@ pub struct DeploymentConfig {
#[partial_default(default_termination_timeout())]
pub termination_timeout: i32,
/// Extra args which are interpolated into the `docker run` command,
/// Extra args which are interpolated into the
/// `docker run` / `docker service create` command,
/// and affect the container configuration.
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
@@ -156,7 +177,8 @@ pub struct DeploymentConfig {
pub extra_args: Vec<String>,
/// Labels attached to various termination signal options.
/// Used to specify different shutdown functionality depending on the termination signal.
/// Used to specify different shutdown functionality depending
/// on the termination signal.
#[serde(default, deserialize_with = "term_labels_deserializer")]
#[partial_attr(serde(
default,
@@ -186,7 +208,7 @@ pub struct DeploymentConfig {
#[builder(default)]
pub volumes: String,
/// The environment variables passed to the container.
/// The environment variables passed to the container / service.
#[serde(default, deserialize_with = "env_vars_deserializer")]
#[partial_attr(serde(
default,
@@ -216,6 +238,10 @@ impl DeploymentConfig {
}
}
fn default_replicas() -> i32 {
1
}
fn default_send_alerts() -> bool {
true
}
@@ -231,26 +257,28 @@ fn default_network() -> String {
impl Default for DeploymentConfig {
fn default() -> Self {
Self {
swarm_id: Default::default(),
server_id: Default::default(),
send_alerts: default_send_alerts(),
links: Default::default(),
image: Default::default(),
image_registry_account: Default::default(),
skip_secret_interp: Default::default(),
redeploy_on_build: Default::default(),
poll_for_updates: Default::default(),
auto_update: Default::default(),
term_signal_labels: Default::default(),
send_alerts: default_send_alerts(),
links: Default::default(),
network: default_network(),
restart: Default::default(),
command: Default::default(),
replicas: default_replicas(),
termination_signal: Default::default(),
termination_timeout: default_termination_timeout(),
extra_args: Default::default(),
term_signal_labels: Default::default(),
ports: Default::default(),
volumes: Default::default(),
environment: Default::default(),
labels: Default::default(),
network: default_network(),
restart: Default::default(),
command: Default::default(),
extra_args: Default::default(),
}
}
}
@@ -399,6 +427,7 @@ impl From<ContainerStateStatusEnum> for DeploymentState {
Default,
Display,
EnumString,
AsRefStr,
)]
pub enum RestartMode {
#[default]

View File

@@ -278,7 +278,19 @@ pub type _PartialStackConfig = PartialStackConfig;
#[partial_derive(Debug, Clone, Default, Serialize, Deserialize)]
#[partial(skip_serializing_none, from, diff)]
pub struct StackConfig {
/// The server to deploy the stack on.
/// The Swarm to deploy the Stack on, setting the Stack into Swarm mode.
///
/// Note. If both swarm_id and server_id are set,
/// swarm_id overrides server_id and the Stack will be in Swarm mode.
#[serde(default, alias = "swarm")]
#[partial_attr(serde(alias = "swarm"))]
#[builder(default)]
pub swarm_id: String,
/// The Server to deploy the Stack on, setting the Stack into Compose mode.
///
/// Note. If both swarm_id and server_id are set,
/// swarm_id overrides server_id and the Stack will be in Swarm mode.
#[serde(default, alias = "server")]
#[partial_attr(serde(alias = "server"))]
#[builder(default)]
@@ -295,9 +307,9 @@ pub struct StackConfig {
/// Optionally specify a custom project name for the stack.
/// If this is empty string, it will default to the stack name.
/// Used with `docker compose -p {project_name}`.
/// Used with `docker compose -p {project_name}` / `docker stack deploy {project_name}`.
///
/// Note. Can be used to import pre-existing stacks.
/// Note. Can be used to import pre-existing stacks with names that do not match Stack name.
#[serde(default)]
#[builder(default)]
pub project_name: String,
@@ -305,6 +317,8 @@ pub struct StackConfig {
/// Whether to automatically `compose pull` before redeploying stack.
/// Ensured latest images are deployed.
/// Will fail if the compose file specifies a locally build image.
///
/// Note. Not used in Swarm mode.
#[serde(default = "default_auto_pull")]
#[builder(default = "default_auto_pull()")]
#[partial_default(default_auto_pull())]
@@ -312,6 +326,8 @@ pub struct StackConfig {
/// Whether to `docker compose build` before `compose down` / `compose up`.
/// Combine with build_extra_args for custom behaviors.
///
/// Note. Not used in Swarm mode.
#[serde(default)]
#[builder(default)]
pub run_build: bool,
@@ -447,6 +463,8 @@ pub struct StackConfig {
/// The name of the written environment file before `docker compose up`.
/// Relative to the run directory root.
/// Default: .env
///
/// Note. Not used in Swarm mode.
#[serde(default = "default_env_file_path")]
#[builder(default = "default_env_file_path()")]
#[partial_default(default_env_file_path())]
@@ -500,7 +518,11 @@ pub struct StackConfig {
#[builder(default)]
pub post_deploy: SystemCommand,
/// The extra arguments to pass after `docker compose up -d`.
/// The extra arguments to pass to the deploy command.
///
/// - For Compose stack, uses `docker compose up -d [EXTRA_ARGS]`.
/// - For Swarm mode. `docker stack deploy [OPTIONS] STACK`
///
/// If empty, no extra arguments will be passed.
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
@@ -513,6 +535,8 @@ pub struct StackConfig {
/// The extra arguments to pass after `docker compose build`.
/// If empty, no extra build arguments will be passed.
/// Only used if `run_build: true`
///
/// Note. Not used in Swarm mode.
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
@@ -560,6 +584,8 @@ pub struct StackConfig {
/// which is given relative to the run directory.
///
/// If it is empty, no file will be written.
///
/// Note. Not used in Swarm mode.
#[serde(default, deserialize_with = "env_vars_deserializer")]
#[partial_attr(serde(
default,
@@ -611,6 +637,7 @@ fn default_send_alerts() -> bool {
impl Default for StackConfig {
fn default() -> Self {
Self {
swarm_id: Default::default(),
server_id: Default::default(),
project_name: Default::default(),
run_directory: Default::default(),
@@ -688,6 +715,9 @@ pub struct StackServiceNames {
///
/// This stores only 1. and 2., ie stacko-mongo.
/// Containers will be matched via regex like `^container_name-?[0-9]*$``
///
/// Note. Setting container_name is not supported by Swarm,
/// so will always be 1. and 2. in Swarm mode.
pub container_name: String,
/// The services image.
#[serde(default)]

View File

@@ -1,16 +1,15 @@
use komodo_client::entities::{
FileContents, RepoExecutionResponse, SearchCombinator,
repo::Repo,
stack::{
Stack, StackFileDependency, StackRemoteFileContents,
StackServiceNames,
},
stack::{Stack, StackFileDependency, StackRemoteFileContents},
update::Log,
};
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::api::DeployStackResponse;
//
/// Get the compose contents on the host, for stacks using
@@ -165,7 +164,7 @@ pub struct ComposePullResponse {
/// docker compose up.
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(ComposeUpResponse)]
#[response(DeployStackResponse)]
#[error(anyhow::Error)]
pub struct ComposeUp {
/// The stack to deploy
@@ -185,31 +184,6 @@ pub struct ComposeUp {
pub replacers: Vec<(String, String)>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ComposeUpResponse {
/// If any of the required files are missing, they will be here.
pub missing_files: Vec<String>,
/// The logs produced by the deploy
pub logs: Vec<Log>,
/// Whether stack was successfully deployed
pub deployed: bool,
/// The stack services.
///
/// Note. The "image" is after interpolation.
#[serde(default)]
pub services: Vec<StackServiceNames>,
/// The deploy compose file contents if they could be acquired, or empty vec.
pub file_contents: Vec<StackRemoteFileContents>,
/// The error in getting remote file contents at the path, or null
pub remote_errors: Vec<FileContents>,
/// The output of `docker compose config` at deploy time
pub compose_config: Option<String>,
/// If its a repo based stack, will include the latest commit hash
pub commit_hash: Option<String>,
/// If its a repo based stack, will include the latest commit message
pub commit_message: Option<String>,
}
//
#[derive(Debug, Clone, Default, Serialize, Deserialize)]

View File

@@ -87,10 +87,12 @@ pub struct GetFullContainerStats {
// ACTIONS
// =======
/// Executes `docker run` to create a container
/// using info given by the Deployment
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct Deploy {
pub struct RunContainer {
pub deployment: Deployment,
pub stop_signal: Option<TerminationSignal>,
pub stop_time: Option<i32>,

View File

@@ -1,5 +1,7 @@
use komodo_client::entities::{
FileContents,
config::{DockerRegistry, GitProvider},
stack::{StackRemoteFileContents, StackServiceNames},
update::Log,
};
use resolver_api::Resolve;
@@ -83,3 +85,27 @@ pub struct ListSecrets {}
#[response(Log)]
#[error(anyhow::Error)]
pub struct PruneSystem {}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DeployStackResponse {
/// If any of the required files are missing, they will be here.
pub missing_files: Vec<String>,
/// The logs produced by the deploy
pub logs: Vec<Log>,
/// Whether stack was successfully deployed
pub deployed: bool,
/// The stack services.
///
/// Note. The "image" is after interpolation.
pub services: Vec<StackServiceNames>,
/// The deploy compose file contents if they could be acquired, or empty vec.
pub file_contents: Vec<StackRemoteFileContents>,
/// The error in getting remote file contents at the path, or null
pub remote_errors: Vec<FileContents>,
/// The output of `docker compose config` / `docker stack config` at deploy time
pub merged_config: Option<String>,
/// If its a repo based stack, will include the latest commit hash
pub commit_hash: Option<String>,
/// If its a repo based stack, will include the latest commit message
pub commit_message: Option<String>,
}

View File

@@ -2,6 +2,7 @@ use std::collections::HashMap;
use komodo_client::entities::{
SearchCombinator,
deployment::Deployment,
docker::{
SwarmLists,
config::SwarmConfig,
@@ -12,11 +13,15 @@ use komodo_client::entities::{
swarm::SwarmInspectInfo,
task::SwarmTask,
},
repo::Repo,
stack::Stack,
update::Log,
};
use resolver_api::Resolve;
use serde::{Deserialize, Serialize};
use crate::api::DeployStackResponse;
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(PollSwarmStatusResponse)]
#[error(anyhow::Error)]
@@ -81,6 +86,26 @@ pub struct InspectSwarmStack {
pub stack: String,
}
/// `docker stack deploy [OPTIONS] STACK`
///
/// https://docs.docker.com/reference/cli/docker/stack/deploy/
#[derive(Debug, Clone, Serialize, Deserialize, Resolve)]
#[response(DeployStackResponse)]
#[error(anyhow::Error)]
pub struct DeploySwarmStack {
/// The stack to deploy
pub stack: Stack,
/// The linked repo, if it exists.
pub repo: Option<Repo>,
/// If provided, use it to login in. Otherwise check periphery local registries.
pub git_token: Option<String>,
/// If provided, use it to login in. Otherwise check periphery local git providers.
pub registry_token: Option<String>,
/// Propogate any secret replacers from core interpolation.
#[serde(default)]
pub replacers: Vec<(String, String)>,
}
/// `docker stack rm [OPTIONS] STACK [STACK...]`
///
/// https://docs.docker.com/reference/cli/docker/stack/rm/
@@ -170,6 +195,21 @@ pub struct GetSwarmServiceLogSearch {
pub details: bool,
}
/// `docker service create [OPTIONS] IMAGE [COMMAND] [ARG...]`
///
/// https://docs.docker.com/reference/cli/docker/service/create/
#[derive(Serialize, Deserialize, Debug, Clone, Resolve)]
#[response(Log)]
#[error(anyhow::Error)]
pub struct CreateSwarmService {
pub deployment: Deployment,
/// Override registry token with one sent from core.
pub registry_token: Option<String>,
/// Propogate any secret replacers from core interpolation.
#[serde(default)]
pub replacers: Vec<(String, String)>,
}
/// `docker service rm SERVICE [SERVICE...]`
///
/// https://docs.docker.com/reference/cli/docker/service/rm/