diff --git a/bin/si/src/main.rs b/bin/si/src/main.rs index 6888935189..f97e427a58 100644 --- a/bin/si/src/main.rs +++ b/bin/si/src/main.rs @@ -1,6 +1,6 @@ use crate::args::{Commands, Engine}; -use color_eyre::Result; -use si_cli::state::AppState; +use color_eyre::{eyre::eyre, Result}; +use si_cli::{state::AppState, DockerClient}; use std::sync::Arc; use telemetry_application::{prelude::*, TelemetryConfig}; use tokio::sync::oneshot::Sender; @@ -32,6 +32,24 @@ async fn main() -> Result<()> { tokio::spawn(wait_for_posthog_flush(ph_done_sender, ph_sender)); + let docker_socket_candidates = vec![ + #[allow(clippy::disallowed_methods)] // Used to determine a path relative to users's home + std::path::Path::new(&std::env::var("HOME")?) + .join(".docker") + .join("run") + .join("docker.sock"), + std::path::Path::new("/var/run/docker.sock").to_path_buf(), + ]; + + let docker_socket = docker_socket_candidates + .iter() + .find(|candidate| candidate.exists()) + .ok_or(eyre!( + "failed to determine Docker socket location; candidates={docker_socket_candidates:?}" + ))?; + + let docker = DockerClient::unix(docker_socket); + let state = AppState::new( ph_client, Arc::from(current_version), @@ -52,7 +70,10 @@ async fn main() -> Result<()> { let auth_api_host = std::env::var("AUTH_API").ok(); if !matches!(args.command, Commands::Update(_)) { - match state.find(current_version, auth_api_host.as_deref()).await { + match state + .find(&docker, current_version, auth_api_host.as_deref()) + .await + { Ok(update) => { if update.si.is_some() { println!("Launcher update found, please run `si update` to install it"); @@ -80,32 +101,33 @@ async fn main() -> Result<()> { match args.command { Commands::Install(_args) => { - state.install().await?; + state.install(&docker).await?; } Commands::Check(_args) => { - state.check(false).await?; + state.check(&docker, false).await?; } Commands::Launch(args) => { state.launch(args.metrics).await?; } Commands::Start(_args) => { - state.start().await?; + state.start(&docker).await?; } Commands::Configure(args) => { state.configure(args.force_reconfigure).await?; } Commands::Delete(_args) => { - state.delete().await?; + state.delete(&docker).await?; } Commands::Restart(_args) => { - state.restart().await?; + state.restart(&docker).await?; } Commands::Stop(_args) => { - state.stop().await?; + state.stop(&docker).await?; } Commands::Update(args) => { state .update( + &docker, current_version, auth_api_host.as_deref(), args.skip_confirmation, @@ -114,7 +136,9 @@ async fn main() -> Result<()> { .await?; } Commands::Status(args) => { - state.status(args.show_logs, args.log_lines).await?; + state + .status(&docker, args.show_logs, args.log_lines) + .await?; } // Commands::Report(_args) => { // state.report().await?; // } diff --git a/lib/si-cli/src/cmd/check.rs b/lib/si-cli/src/cmd/check.rs index daa8276617..ef78b6b676 100644 --- a/lib/si-cli/src/cmd/check.rs +++ b/lib/si-cli/src/cmd/check.rs @@ -1,22 +1,22 @@ +use crate::containers::DockerClient; use crate::key_management::get_user_email; use crate::state::AppState; use crate::{CliResult, SiCliError}; use comfy_table::presets::UTF8_FULL; use comfy_table::*; -use docker_api::Docker; impl AppState { - pub async fn check(&self, silent: bool) -> CliResult<()> { + pub async fn check(&self, docker: &DockerClient, silent: bool) -> CliResult<()> { self.track( get_user_email().await?, serde_json::json!({"command-name": "check-dependencies"}), ); - invoke(silent, self.is_preview()).await?; + invoke(docker, silent, self.is_preview()).await?; Ok(()) } } -async fn invoke(silent: bool, is_preview: bool) -> CliResult<()> { +async fn invoke(docker: &DockerClient, silent: bool, is_preview: bool) -> CliResult<()> { if !silent { println!("Checking that the system is able to interact with the docker engine to control System Initiative..."); } @@ -25,7 +25,6 @@ async fn invoke(silent: bool, is_preview: bool) -> CliResult<()> { return Ok(()); } - let docker = Docker::unix("//var/run/docker.sock"); if let Err(_e) = docker.ping().await { return Err(SiCliError::DockerEngine); } diff --git a/lib/si-cli/src/cmd/delete.rs b/lib/si-cli/src/cmd/delete.rs index 5d4964aeeb..432ea55276 100644 --- a/lib/si-cli/src/cmd/delete.rs +++ b/lib/si-cli/src/cmd/delete.rs @@ -1,24 +1,21 @@ -use crate::containers::{cleanup_image, delete_container, get_existing_container}; +use crate::containers::DockerClient; use crate::key_management::get_user_email; use crate::state::AppState; use crate::{CliResult, CONTAINER_NAMES}; -use docker_api::Docker; impl AppState { - pub async fn delete(&self) -> CliResult<()> { + pub async fn delete(&self, docker: &DockerClient) -> CliResult<()> { self.track( get_user_email().await?, serde_json::json!({"command-name": "delete-system"}), ); - invoke(self, self.is_preview()).await?; + invoke(self, docker, self.is_preview()).await?; Ok(()) } } -async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { - app.check(true).await?; - - let docker = Docker::unix("//var/run/docker.sock"); +async fn invoke(app: &AppState, docker: &DockerClient, is_preview: bool) -> CliResult<()> { + app.check(docker, true).await?; if is_preview { println!("Deleted the following containers and associated images:"); @@ -30,10 +27,14 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { println!("{}", container_name); continue; } - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(container_summary) = container_summary { - delete_container(&docker, container_summary, container_name.clone()).await?; - cleanup_image(&docker, name.to_string()).await?; + docker + .delete_container(container_summary, container_name.clone()) + .await?; + docker.cleanup_image(name.to_string()).await?; } } diff --git a/lib/si-cli/src/cmd/install.rs b/lib/si-cli/src/cmd/install.rs index 0c10031f41..43b8e72632 100644 --- a/lib/si-cli/src/cmd/install.rs +++ b/lib/si-cli/src/cmd/install.rs @@ -1,21 +1,21 @@ -use crate::containers::{download_missing_containers, missing_containers}; +use crate::containers::DockerClient; use crate::key_management::get_user_email; use crate::state::AppState; use crate::CliResult; impl AppState { - pub async fn install(&self) -> CliResult<()> { + pub async fn install(&self, docker: &DockerClient) -> CliResult<()> { self.track( get_user_email().await?, serde_json::json!({"command-name": "install"}), ); - invoke(self.is_preview()).await?; + invoke(docker, self.is_preview()).await?; Ok(()) } } -async fn invoke(is_preview: bool) -> CliResult<()> { - let missing_containers = missing_containers().await?; +async fn invoke(docker: &DockerClient, is_preview: bool) -> CliResult<()> { + let missing_containers = docker.missing_containers().await?; if missing_containers.is_empty() { println!("All containers downloaded\n"); return Ok(()); @@ -30,7 +30,9 @@ async fn invoke(is_preview: bool) -> CliResult<()> { } println!("Downloading the containers required to run System Initiative"); - download_missing_containers(missing_containers).await?; + docker + .download_missing_containers(missing_containers) + .await?; Ok(()) } diff --git a/lib/si-cli/src/cmd/restart.rs b/lib/si-cli/src/cmd/restart.rs index 769843605a..c3093864d7 100644 --- a/lib/si-cli/src/cmd/restart.rs +++ b/lib/si-cli/src/cmd/restart.rs @@ -1,21 +1,22 @@ +use crate::containers::DockerClient; use crate::key_management::get_user_email; use crate::state::AppState; use crate::CliResult; impl AppState { - pub async fn restart(&self) -> CliResult<()> { + pub async fn restart(&self, docker: &DockerClient) -> CliResult<()> { self.track( get_user_email().await?, serde_json::json!({"command-name": "restart-system"}), ); - invoke(self).await?; + invoke(self, docker).await?; Ok(()) } } -async fn invoke(app: &AppState) -> CliResult<()> { - app.stop().await?; - app.start().await?; +async fn invoke(app: &AppState, docker: &DockerClient) -> CliResult<()> { + app.stop(docker).await?; + app.start(docker).await?; Ok(()) } diff --git a/lib/si-cli/src/cmd/start.rs b/lib/si-cli/src/cmd/start.rs index 29999f495f..76fda00fd7 100644 --- a/lib/si-cli/src/cmd/start.rs +++ b/lib/si-cli/src/cmd/start.rs @@ -1,4 +1,4 @@ -use crate::containers::get_existing_container; +use crate::containers::DockerClient; use crate::key_management::{ ensure_encryption_keys, ensure_jwt_public_signing_key, format_credentials_for_veritech, get_si_data_dir, get_user_email, @@ -6,25 +6,22 @@ use crate::key_management::{ use crate::state::AppState; use crate::{CliResult, CONTAINER_NAMES}; use docker_api::opts::{ContainerCreateOpts, HostPort, PublishPort}; -use docker_api::Docker; impl AppState { - pub async fn start(&self) -> CliResult<()> { + pub async fn start(&self, docker: &DockerClient) -> CliResult<()> { self.track( get_user_email().await?, serde_json::json!({"command-name": "start-system"}), ); - invoke(self, self.is_preview()).await?; + invoke(self, docker, self.is_preview()).await?; Ok(()) } } -async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { +async fn invoke(app: &AppState, docker: &DockerClient, is_preview: bool) -> CliResult<()> { app.configure(false).await?; - app.check(false).await?; - app.install().await?; - - let docker = Docker::unix("//var/run/docker.sock"); + app.check(docker, false).await?; + app.install(docker).await?; if is_preview { println!("Started the following containers:"); @@ -38,7 +35,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { let container = format!("systeminit/{0}", name); let container_name = format!("local-{0}-1", name); if container == "systeminit/otelcol" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -77,7 +76,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/jaeger" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -115,7 +116,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/nats" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -153,7 +156,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/postgres" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -196,7 +201,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/council" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -237,7 +244,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/veritech" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -282,7 +291,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/pinga" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -330,7 +341,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/sdf" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here @@ -388,7 +401,9 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { container.start().await?; } if container == "systeminit/web" { - let container_summary = get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(existing) = container_summary { // it means we have an existing container // If it's running, we have nothing to do here diff --git a/lib/si-cli/src/cmd/status.rs b/lib/si-cli/src/cmd/status.rs index 8fb5a441ba..cd920170bb 100644 --- a/lib/si-cli/src/cmd/status.rs +++ b/lib/si-cli/src/cmd/status.rs @@ -1,29 +1,33 @@ -use crate::containers::{get_container_logs, get_existing_container}; +use comfy_table::presets::UTF8_FULL; +use comfy_table::*; + +use crate::containers::DockerClient; use crate::key_management::get_user_email; use crate::state::AppState; use crate::{CliResult, CONTAINER_NAMES}; -use comfy_table::presets::UTF8_FULL; -use comfy_table::*; -use docker_api::Docker; const RUNNING: &str = " ✅ "; const NOT_RUNNING: &str = " ❌ "; const WAITING: &str = " 🕒 "; impl AppState { - pub async fn status(&self, show_logs: bool, log_lines: usize) -> CliResult<()> { + pub async fn status( + &self, + docker: &DockerClient, + show_logs: bool, + log_lines: usize, + ) -> CliResult<()> { self.track( get_user_email().await?, serde_json::json!({"command-name": "system-status"}), ); - invoke(show_logs, log_lines).await?; + invoke(docker, show_logs, log_lines).await?; Ok(()) } } -async fn invoke(show_logs: bool, log_lines: usize) -> CliResult<()> { +async fn invoke(docker: &DockerClient, show_logs: bool, log_lines: usize) -> CliResult<()> { println!("Checking the status of System Initiative Software"); - let docker = Docker::unix("//var/run/docker.sock"); let mut table = Table::new(); table .load_preset(UTF8_FULL) @@ -38,8 +42,9 @@ async fn invoke(show_logs: bool, log_lines: usize) -> CliResult<()> { for name in CONTAINER_NAMES.iter() { let image_name = format!("systeminit/{0}:stable", name); let container_identifier = format!("local-{0}-1", name); - let existing_container = - get_existing_container(&docker, container_identifier.clone()).await?; + let existing_container = docker + .get_existing_container(container_identifier.clone()) + .await?; let mut state = "".to_string(); if let Some(existing) = existing_container { state = existing.state.unwrap(); @@ -52,7 +57,9 @@ async fn invoke(show_logs: bool, log_lines: usize) -> CliResult<()> { if show_logs { println!("\n\nShowing container logs for {0}", image_name.clone()); - get_container_logs(&docker, container_identifier.clone(), log_lines).await?; + docker + .get_container_logs(container_identifier.clone(), log_lines) + .await?; } if container_identifier == "local-web-1" { diff --git a/lib/si-cli/src/cmd/stop.rs b/lib/si-cli/src/cmd/stop.rs index 5204bfa49e..fa4623f641 100644 --- a/lib/si-cli/src/cmd/stop.rs +++ b/lib/si-cli/src/cmd/stop.rs @@ -1,22 +1,22 @@ +use crate::containers::DockerClient; use crate::key_management::get_user_email; use crate::state::AppState; use crate::{CliResult, CONTAINER_NAMES}; use docker_api::opts::{ContainerFilter, ContainerListOpts, ContainerStopOpts}; -use docker_api::Docker; impl AppState { - pub async fn stop(&self) -> CliResult<()> { + pub async fn stop(&self, docker: &DockerClient) -> CliResult<()> { self.track( get_user_email().await?, serde_json::json!({"command-name": "check-dependencies"}), ); - invoke(self, self.is_preview()).await?; + invoke(self, docker, self.is_preview()).await?; Ok(()) } } -async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { - app.check(true).await?; +async fn invoke(app: &AppState, docker: &DockerClient, is_preview: bool) -> CliResult<()> { + app.check(docker, true).await?; if is_preview { println!("Stopped the following containers:"); @@ -28,7 +28,6 @@ async fn invoke(app: &AppState, is_preview: bool) -> CliResult<()> { println!("{}", container_identifier.clone()); continue; } - let docker = Docker::unix("//var/run/docker.sock"); let filter = ContainerFilter::Name(container_identifier.clone()); let list_opts = ContainerListOpts::builder() .filter([filter]) diff --git a/lib/si-cli/src/cmd/update.rs b/lib/si-cli/src/cmd/update.rs index 061e80fe99..07e69f8cf2 100644 --- a/lib/si-cli/src/cmd/update.rs +++ b/lib/si-cli/src/cmd/update.rs @@ -1,11 +1,8 @@ -use crate::containers::{ - cleanup_image, delete_container, get_container_details, get_existing_container, -}; +use crate::containers::DockerClient; use crate::key_management::get_user_email; use crate::state::AppState; use crate::{CliResult, SiCliError}; use colored::Colorize; -use docker_api::Docker; use flate2::read::GzDecoder; use inquire::Confirm; use serde::{Deserialize, Serialize}; @@ -100,6 +97,7 @@ async fn update_current_binary(url: &str) -> CliResult<()> { impl AppState { pub async fn update( &self, + docker: &DockerClient, current_version: &str, host: Option<&str>, skip_confirmation: bool, @@ -109,11 +107,24 @@ impl AppState { get_user_email().await?, serde_json::json!({"command-name": "update-launcher"}), ); - invoke(self, current_version, host, skip_confirmation, only_binary).await?; + invoke( + self, + docker, + current_version, + host, + skip_confirmation, + only_binary, + ) + .await?; Ok(()) } - pub async fn find(&self, current_version: &str, host: Option<&str>) -> CliResult { + pub async fn find( + &self, + docker: &DockerClient, + current_version: &str, + host: Option<&str>, + ) -> CliResult { let host = if let Some(host) = host { host } else { HOST }; let req = reqwest::get(format!("{host}/github/containers/latest")).await?; @@ -123,7 +134,7 @@ impl AppState { )); } - let current_containers = get_container_details().await?; + let current_containers = docker.get_container_details().await?; let mut containers = Vec::new(); let latest_containers: Vec = req.json().await?; @@ -164,6 +175,7 @@ impl AppState { async fn invoke( app: &AppState, + docker: &DockerClient, current_version: &str, host: Option<&str>, skip_confirmation: bool, @@ -175,7 +187,7 @@ async fn invoke( #[cfg(all(not(target_os = "linux"), target_vendor = "apple"))] let our_os = "Darwin"; - let update = app.find(current_version, host).await?; + let update = app.find(docker, current_version, host).await?; if !only_binary { for image in &update.containers { println!( @@ -222,21 +234,22 @@ async fn invoke( match ans { Ok(true) => { if !only_binary && !update.containers.is_empty() { - app.stop().await?; + app.stop(docker).await?; - let docker = Docker::unix("//var/run/docker.sock"); for container in &update.containers { let container_name = format!("local-{0}-1", container.repository); - let container_summary = - get_existing_container(&docker, container_name.clone()).await?; + let container_summary = docker + .get_existing_container(container_name.clone()) + .await?; if let Some(container_summary) = container_summary { - delete_container(&docker, container_summary, container_name.clone()) + docker + .delete_container(container_summary, container_name.clone()) .await?; - cleanup_image(&docker, container_name.to_string()).await?; + docker.cleanup_image(container_name.to_string()).await?; } } - app.start().await?; + app.start(docker).await?; app.track( get_user_email().await?, diff --git a/lib/si-cli/src/containers.rs b/lib/si-cli/src/containers.rs index 84d5a5fa19..4d7bf015df 100644 --- a/lib/si-cli/src/containers.rs +++ b/lib/si-cli/src/containers.rs @@ -1,6 +1,6 @@ use crate::SiCliError; use crate::{CliResult, CONTAINER_NAMES}; -use docker_api::models::{ContainerSummary, ImageSummary}; +use docker_api::models::{ContainerSummary, ImageSummary, PingInfo}; use docker_api::opts::{ ContainerFilter, ContainerListOpts, ImageListOpts, ImageRemoveOpts, LogsOpts, PullOpts, RegistryAuth, @@ -9,7 +9,9 @@ use docker_api::Docker; use futures::StreamExt; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use std::cmp::min; +use std::path::Path; use std::string::ToString; +use telemetry::prelude::*; #[derive(Debug)] pub struct DockerReleaseInfo { @@ -18,226 +20,257 @@ pub struct DockerReleaseInfo { pub image: String, } -pub(crate) async fn downloaded_systeminit_containers_list() -> Result, SiCliError> -{ - let docker = Docker::unix("//var/run/docker.sock"); - let opts = ImageListOpts::builder().all(true).build(); - let mut containers = docker.images().list(&opts).await?; - - let containers: Vec = containers - .drain(..) - .filter(|c| { - c.repo_tags - .iter() - .any(|t| t.starts_with("systeminit/") && t.ends_with(":stable")) - }) - .collect(); - - Ok(containers) +#[derive(Clone, Debug)] +pub struct DockerClient { + docker: Docker, } -pub(crate) async fn get_container_details() -> CliResult> { - let mut release_info: Vec = Vec::new(); - let containers = downloaded_systeminit_containers_list().await?; - for container in containers { - // Each of the containers we use will 100% have these labels so it's fine to unwrap them - // it's not the ideal and we can find a better way to deal with the option but it works - release_info.push(DockerReleaseInfo { - git_sha: container - .labels - .get("org.opencontainers.image.revision") - .unwrap() - .to_string(), - created_at: container - .labels - .get("org.opencontainers.image.created") - .unwrap() - .to_string(), - image: container.labels.get("name").unwrap().to_string(), - }) +impl DockerClient { + pub fn unix(socket_path: impl AsRef) -> Self { + debug!( + socket_path = %socket_path.as_ref().display(), + "configuring Docker with unix socket" + ); + Self { + docker: Docker::unix(socket_path), + } } - Ok(release_info) -} + pub(crate) fn containers(&self) -> docker_api::Containers { + self.docker.containers() + } + + pub(crate) async fn ping(&self) -> CliResult { + self.docker.ping().await.map_err(Into::into) + } + + pub(crate) async fn downloaded_systeminit_containers_list( + &self, + ) -> Result, SiCliError> { + let opts = ImageListOpts::builder().all(true).build(); + let mut containers = self.docker.images().list(&opts).await?; -pub(crate) async fn missing_containers() -> Result, SiCliError> { - let mut missing_containers = Vec::new(); - let containers = downloaded_systeminit_containers_list().await?; - - for name in CONTAINER_NAMES.iter() { - let required_container = format!("systeminit/{0}", name); - if !containers.iter().any(|c| { - c.repo_tags - .iter() - .all(|t| t.contains(required_container.as_str())) - }) { - missing_containers.push(required_container.to_string()); + let containers: Vec = containers + .drain(..) + .filter(|c| { + c.repo_tags + .iter() + .any(|t| t.starts_with("systeminit/") && t.ends_with(":stable")) + }) + .collect(); + + Ok(containers) + } + + pub(crate) async fn get_container_details(&self) -> CliResult> { + let mut release_info: Vec = Vec::new(); + let containers = self.downloaded_systeminit_containers_list().await?; + for container in containers { + // Each of the containers we use will 100% have these labels so it's fine to unwrap them + // it's not the ideal and we can find a better way to deal with the option but it works + release_info.push(DockerReleaseInfo { + git_sha: container + .labels + .get("org.opencontainers.image.revision") + .unwrap() + .to_string(), + created_at: container + .labels + .get("org.opencontainers.image.created") + .unwrap() + .to_string(), + image: container.labels.get("name").unwrap().to_string(), + }) } + + Ok(release_info) } - Ok(missing_containers) -} + pub(crate) async fn missing_containers(&self) -> Result, SiCliError> { + let mut missing_containers = Vec::new(); + let containers = self.downloaded_systeminit_containers_list().await?; + + for name in CONTAINER_NAMES.iter() { + let required_container = format!("systeminit/{0}", name); + if !containers.iter().any(|c| { + c.repo_tags + .iter() + .all(|t| t.contains(required_container.as_str())) + }) { + missing_containers.push(required_container.to_string()); + } + } + + Ok(missing_containers) + } + + pub(crate) async fn download_missing_containers( + &self, + missing_containers: Vec, + ) -> CliResult<()> { + let m = MultiProgress::new(); + let sty = ProgressStyle::with_template( + "{spinner:.red} [{elapsed_precise}] [{wide_bar:.yellow/blue}]", + ) + .unwrap() + .progress_chars("#>-"); + + let total_size = 100123123; + + println!("Found {0} missing containers", missing_containers.len()); + + let mut spawned = Vec::new(); + for missing_container in missing_containers { + let pb = m.add(ProgressBar::new(total_size)); + pb.set_style(sty.clone()); + + let mut message = "Downloading ".to_owned(); + message.push_str(missing_container.as_str()); -pub(crate) async fn download_missing_containers(missing_containers: Vec) -> CliResult<()> { - let m = MultiProgress::new(); - let sty = ProgressStyle::with_template( - "{spinner:.red} [{elapsed_precise}] [{wide_bar:.yellow/blue}]", - ) - .unwrap() - .progress_chars("#>-"); - - let total_size = 100123123; - - println!("Found {0} missing containers", missing_containers.len()); - - let mut spawned = Vec::new(); - for missing_container in missing_containers { - let pb = m.add(ProgressBar::new(total_size)); - pb.set_style(sty.clone()); - - let mut message = "Downloading ".to_owned(); - message.push_str(missing_container.as_str()); - - let h1 = tokio::spawn(async move { - let docker = Docker::unix("//var/run/docker.sock"); - let mut downloaded = 0; - - let auth = RegistryAuth::builder() - .username("stack72") - .password("dckr_pat_dHhJ3jhygqHx2gCCZqchywQEvDQ") - .build(); - let pull_opts = PullOpts::builder() - .image(missing_container) - .tag("stable") - .auth(auth) - .build(); - let images = docker.images(); - let mut stream = images.pull(&pull_opts); - while let Some(pull_result) = stream.next().await { - match pull_result { - Ok(docker_api::models::ImageBuildChunk::PullStatus { - progress_detail, .. - }) => { - if let Some(progress_detail) = progress_detail { - let new = min( - downloaded + progress_detail.current.unwrap_or(0), - total_size, - ); - downloaded = progress_detail.current.unwrap_or(0); - pb.set_position(new); + let docker = self.docker.clone(); + + let h1 = tokio::spawn(async move { + let mut downloaded = 0; + + let auth = RegistryAuth::builder() + .username("stack72") + .password("dckr_pat_dHhJ3jhygqHx2gCCZqchywQEvDQ") + .build(); + let pull_opts = PullOpts::builder() + .image(missing_container) + .tag("stable") + .auth(auth) + .build(); + let images = docker.images(); + let mut stream = images.pull(&pull_opts); + while let Some(pull_result) = stream.next().await { + match pull_result { + Ok(docker_api::models::ImageBuildChunk::PullStatus { + progress_detail, + .. + }) => { + if let Some(progress_detail) = progress_detail { + let new = min( + downloaded + progress_detail.current.unwrap_or(0), + total_size, + ); + downloaded = progress_detail.current.unwrap_or(0); + pb.set_position(new); + } } + Ok(_) => {} + Err(e) => eprintln!("{e}"), } - Ok(_) => {} - Err(e) => eprintln!("{e}"), } - } - }); + }); + + m.println(message).unwrap(); + + spawned.push(h1); + } + + for spawn in spawned { + spawn.await.unwrap(); + } - m.println(message).unwrap(); + m.println("All containers successfully downloaded").unwrap(); + m.clear().unwrap(); - spawned.push(h1); + Ok(()) } - for spawn in spawned { - spawn.await.unwrap(); + pub(crate) async fn delete_container( + &self, + container_summary: ContainerSummary, + name: String, + ) -> CliResult<()> { + println!( + "Deleting container: {} ({})", + name, + container_summary.id.as_ref().unwrap() + ); + let container = self + .docker + .containers() + .get(container_summary.id.as_ref().unwrap()); + container.delete().await?; + Ok(()) } - m.println("All containers successfully downloaded").unwrap(); - m.clear().unwrap(); + pub(crate) async fn get_existing_container( + &self, + name: String, + ) -> CliResult> { + let filter = ContainerFilter::Name(name.clone()); + let list_opts = ContainerListOpts::builder() + .filter([filter]) + .all(true) + .build(); - Ok(()) -} + let mut containers = self.docker.containers().list(&list_opts).await?; + Ok(containers.pop()) + } -pub(crate) async fn delete_container( - docker: &Docker, - container_summary: ContainerSummary, - name: String, -) -> CliResult<()> { - println!( - "Deleting container: {} ({})", - name, - container_summary.id.as_ref().unwrap() - ); - let container = docker - .containers() - .get(container_summary.id.as_ref().unwrap()); - container.delete().await?; - Ok(()) -} + pub(crate) async fn cleanup_image(&self, name: String) -> CliResult<()> { + let image_name = format!("systeminit/{0}:stable", name); + let opts = ImageRemoveOpts::builder() + .force(true) + .noprune(false) + .build(); -pub(crate) async fn get_existing_container( - docker: &Docker, - name: String, -) -> CliResult> { - let filter = ContainerFilter::Name(name.clone()); - let list_opts = ContainerListOpts::builder() - .filter([filter]) - .all(true) - .build(); - - let mut containers = docker.containers().list(&list_opts).await?; - Ok(containers.pop()) -} + if (self.docker.images().get(image_name.clone()).inspect().await).is_ok() { + println!("Removing image: {0}", image_name.clone()); + self.docker + .images() + .get(image_name.clone()) + .remove(&opts) + .await?; + }; -pub(crate) async fn cleanup_image(docker: &Docker, name: String) -> CliResult<()> { - let image_name = format!("systeminit/{0}:stable", name); - let opts = ImageRemoveOpts::builder() - .force(true) - .noprune(false) - .build(); - - if (docker.images().get(image_name.clone()).inspect().await).is_ok() { - println!("Removing image: {0}", image_name.clone()); - docker - .images() - .get(image_name.clone()) - .remove(&opts) - .await?; - }; - - Ok(()) -} + Ok(()) + } -pub(crate) async fn get_container_logs( - docker: &Docker, - name: String, - log_lines: usize, -) -> CliResult { - let filter = ContainerFilter::Name(name.clone()); - let list_opts = ContainerListOpts::builder() - .filter([filter]) - .all(true) - .build(); - let containers = docker.containers().list(&list_opts).await?; - if !containers.is_empty() { - let existing_id = containers.first().unwrap().id.as_ref().unwrap(); - let state = containers.first().unwrap().state.as_ref().unwrap(); - - if *state == "running" { - let logs_opts = LogsOpts::builder() - .n_lines(log_lines) - .stdout(true) - .stderr(true) - .build(); - let container = docker.containers().get(existing_id); - let logs_stream = container.logs(&logs_opts); - let logs: Vec<_> = logs_stream - .map(|chunk| match chunk { - Ok(chunk) => chunk.to_vec(), - Err(e) => { - eprintln!("Error: {e}"); - vec![] - } - }) - .collect::>() - .await - .into_iter() - .flatten() - .collect::>(); - println!("{}", String::from_utf8_lossy(&logs)); - return Ok(true); + pub(crate) async fn get_container_logs( + &self, + name: String, + log_lines: usize, + ) -> CliResult { + let filter = ContainerFilter::Name(name.clone()); + let list_opts = ContainerListOpts::builder() + .filter([filter]) + .all(true) + .build(); + let containers = self.docker.containers().list(&list_opts).await?; + if !containers.is_empty() { + let existing_id = containers.first().unwrap().id.as_ref().unwrap(); + let state = containers.first().unwrap().state.as_ref().unwrap(); + + if *state == "running" { + let logs_opts = LogsOpts::builder() + .n_lines(log_lines) + .stdout(true) + .stderr(true) + .build(); + let container = self.docker.containers().get(existing_id); + let logs_stream = container.logs(&logs_opts); + let logs: Vec<_> = logs_stream + .map(|chunk| match chunk { + Ok(chunk) => chunk.to_vec(), + Err(e) => { + eprintln!("Error: {e}"); + vec![] + } + }) + .collect::>() + .await + .into_iter() + .flatten() + .collect::>(); + println!("{}", String::from_utf8_lossy(&logs)); + return Ok(true); + } } - } - Ok(false) + Ok(false) + } } diff --git a/lib/si-cli/src/lib.rs b/lib/si-cli/src/lib.rs index 5f39ac5bbe..e1fc8abdbd 100644 --- a/lib/si-cli/src/lib.rs +++ b/lib/si-cli/src/lib.rs @@ -6,6 +6,8 @@ mod containers; mod key_management; pub mod state; +pub use containers::DockerClient; + pub const CONTAINER_NAMES: &[&str] = &[ "jaeger", "postgres", "nats", "otelcol", "council", "veritech", "pinga", "sdf", "web", ];