feat(logs): implement streaming logs with follow option
This commit is contained in:
@@ -43,7 +43,10 @@ pub enum ControlCommands {
|
||||
/// Status of instance
|
||||
Status,
|
||||
/// View logs produced by postgres
|
||||
Logs { follow: bool },
|
||||
Logs {
|
||||
#[arg(short, long, default_value = "false")]
|
||||
follow: bool,
|
||||
},
|
||||
/// (Sensitive) get connection details
|
||||
Connection {
|
||||
#[arg(short, long, default_value = "dsn")]
|
||||
|
||||
@@ -3,6 +3,7 @@ use miette::miette;
|
||||
|
||||
use colored::Colorize;
|
||||
use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table, presets::UTF8_FULL};
|
||||
use futures::TryStreamExt;
|
||||
use miette::Result;
|
||||
|
||||
use crate::{
|
||||
@@ -65,6 +66,24 @@ impl Controller {
|
||||
Self { ctx }
|
||||
}
|
||||
|
||||
pub async fn logs(&self, follow: bool) -> Result<()> {
|
||||
let instance = self.ctx.require_instance()?;
|
||||
|
||||
let mut logs = self
|
||||
.ctx
|
||||
.docker
|
||||
.stream_logs(&instance.container_id, follow)
|
||||
.await;
|
||||
|
||||
while let Some(log) = logs.try_next().await? {
|
||||
let bytes = log.into_bytes();
|
||||
let line = String::from_utf8_lossy(bytes.as_ref());
|
||||
print!("{line}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn show_connection(&self, format: ConnectionFormat) -> Result<()> {
|
||||
let project = self.ctx.require_project()?;
|
||||
let reconciler = Reconciler { ctx: &self.ctx };
|
||||
@@ -84,34 +103,7 @@ impl Controller {
|
||||
println!("{}", dsn.to_string());
|
||||
}
|
||||
ConnectionFormat::Human => {
|
||||
let mut table = create_ui_table("Instance");
|
||||
table.add_row(vec![
|
||||
Cell::new("Project").fg(Color::White),
|
||||
Cell::new(&project.name).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
table.add_row(vec![
|
||||
Cell::new("PostgreSQL Version").fg(Color::White),
|
||||
Cell::new(project.config.version.to_string()).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
table.add_row(vec![
|
||||
Cell::new("Host").fg(Color::White),
|
||||
Cell::new("127.0.0.1").add_attribute(Attribute::Bold),
|
||||
]);
|
||||
|
||||
table.add_row(vec![
|
||||
Cell::new("Port").fg(Color::White),
|
||||
Cell::new(project.config.port.to_string()).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
table.add_row(vec![
|
||||
Cell::new("Username").fg(Color::White),
|
||||
Cell::new(USERNAME).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
|
||||
table.add_row(vec![
|
||||
Cell::new("Password").fg(Color::White),
|
||||
Cell::new(project.config.password.clone()).fg(Color::DarkGrey),
|
||||
]);
|
||||
println!("{}", table);
|
||||
format_conn_human(project);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +166,37 @@ impl Controller {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_conn_human(project: &Project) {
|
||||
let mut table = create_ui_table("Instance");
|
||||
table.add_row(vec![
|
||||
Cell::new("Project").fg(Color::White),
|
||||
Cell::new(&project.name).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
table.add_row(vec![
|
||||
Cell::new("PostgreSQL Version").fg(Color::White),
|
||||
Cell::new(project.config.version.to_string()).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
table.add_row(vec![
|
||||
Cell::new("Host").fg(Color::White),
|
||||
Cell::new("127.0.0.1").add_attribute(Attribute::Bold),
|
||||
]);
|
||||
|
||||
table.add_row(vec![
|
||||
Cell::new("Port").fg(Color::White),
|
||||
Cell::new(project.config.port.to_string()).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
table.add_row(vec![
|
||||
Cell::new("Username").fg(Color::White),
|
||||
Cell::new(USERNAME).add_attribute(Attribute::Bold),
|
||||
]);
|
||||
|
||||
table.add_row(vec![
|
||||
Cell::new("Password").fg(Color::White),
|
||||
Cell::new(project.config.password.clone()).fg(Color::DarkGrey),
|
||||
]);
|
||||
println!("{}", table);
|
||||
}
|
||||
|
||||
fn create_ui_table(header: &'static str) -> Table {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
|
||||
@@ -4,6 +4,7 @@ use thiserror::Error;
|
||||
|
||||
use bollard::{
|
||||
Docker,
|
||||
container::LogOutput,
|
||||
query_parameters::{
|
||||
CreateContainerOptions, CreateImageOptions, InspectContainerOptions, ListImagesOptions,
|
||||
LogsOptions, StartContainerOptions, StopContainerOptions,
|
||||
@@ -11,7 +12,7 @@ use bollard::{
|
||||
secret::ContainerCreateBody,
|
||||
};
|
||||
use colored::Colorize;
|
||||
use futures::StreamExt;
|
||||
use futures::{Stream, StreamExt};
|
||||
use indicatif::MultiProgress;
|
||||
use miette::{Context, IntoDiagnostic, Result};
|
||||
use tracing::info;
|
||||
@@ -270,7 +271,11 @@ impl DockerController {
|
||||
.map_err(|_| miette!("Invalid version in label: {}", version_str))
|
||||
}
|
||||
|
||||
pub async fn stream_logs(&self, container_id: &str, follow: bool) -> Result<()> {
|
||||
pub async fn stream_logs(
|
||||
&self,
|
||||
container_id: &str,
|
||||
follow: bool,
|
||||
) -> impl Stream<Item = Result<LogOutput>> {
|
||||
let options = Some(LogsOptions {
|
||||
follow,
|
||||
stdout: true,
|
||||
@@ -278,22 +283,11 @@ impl DockerController {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut logs = self.daemon.logs(container_id, options);
|
||||
let logs = self
|
||||
.daemon
|
||||
.logs(container_id, options)
|
||||
.map(|k| k.into_diagnostic().wrap_err("Failed streaming logs"));
|
||||
|
||||
while let Some(entry) = logs.next().await {
|
||||
match entry {
|
||||
Ok(output) => {
|
||||
print!("{output}");
|
||||
std::io::stdout().flush().ok();
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err)
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to stream container logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
logs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ async fn main() -> Result<()> {
|
||||
ControlCommands::Stop => {}
|
||||
ControlCommands::Restart => {}
|
||||
ControlCommands::Destroy { accept } => {}
|
||||
ControlCommands::Logs { follow } => todo!(),
|
||||
ControlCommands::Logs { follow } => {
|
||||
let ctx = Context::new(name).await?;
|
||||
Controller::new(ctx).logs(follow).await?;
|
||||
}
|
||||
ControlCommands::Status => {}
|
||||
// can't override an instance for this command, because password is in config
|
||||
ControlCommands::Connection { format } => {
|
||||
|
||||
Reference in New Issue
Block a user