From d4fc01fed7aed381c77deac9e17e34b6e0431a8b Mon Sep 17 00:00:00 2001 From: hengfei yang Date: Tue, 8 Apr 2025 12:56:31 +0800 Subject: [PATCH] fix: permission of data directory --- Cargo.lock | 80 +++++++++++---- Cargo.toml | 9 +- Dockerfile | 1 + rust-toolchain.toml | 2 +- src/cli/mod.rs | 52 ++++++++++ src/cli/utils.rs | 29 ++++++ src/config.rs | 234 +++++++++++++++++++++----------------------- src/lib.rs | 15 ++- src/main.rs | 8 +- src/router.rs | 21 +++- 10 files changed, 296 insertions(+), 155 deletions(-) create mode 100644 src/cli/mod.rs create mode 100644 src/cli/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 2b5a89a..54bc640 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix-codec" @@ -102,7 +102,7 @@ dependencies = [ "actix-utils", "futures-core", "futures-util", - "mio", + "mio 0.8.11", "socket2", "tokio", "tracing", @@ -254,6 +254,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + [[package]] name = "anyhow" version = "1.0.82" @@ -550,6 +556,32 @@ dependencies = [ "stacker", ] +[[package]] +name = "clap" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -1331,6 +1363,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nom" version = "7.1.3" @@ -1356,24 +1400,15 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "o2_report_generator" -version = "0.1.0" +version = "0.11.0" dependencies = [ "actix-web", "anyhow", "chromiumoxide", "chrono", + "clap", "dotenv_config", "dotenvy", "env_logger", @@ -1920,6 +1955,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.5.0" @@ -2031,28 +2072,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.2", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4b9a39d..d826e10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "o2_report_generator" -version = "0.1.0" +version = "0.11.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,6 +13,13 @@ chromiumoxide = { git = "https://github.com/mattsse/chromiumoxide", features = [ "_fetcher-rusttls-tokio", ], default-features = false, rev = "6f2392f78ae851e2acf33df8e9764cc299d837db" } chrono = { version = "0.4", default-features = false, features = ["clock"] } +clap = { version = "4.5", default-features = false, features = [ + "std", + "help", + "usage", + "suggestions", + "cargo", +] } dotenv_config = "0.1" dotenvy = "0.15" env_logger = "0.10" diff --git a/Dockerfile b/Dockerfile index 2f9cc05..9506352 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,5 @@ FROM public.ecr.aws/debian/debian:bookworm-slim as runtime RUN apt-get update -y && apt-get install -y --no-install-recommends ca-certificates chromium && \ update-ca-certificates COPY ./bin/report-generator / +RUN ["/report-generator", "init-dir", "-p", "/data/"] CMD ["/report-generator"] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5ff8c91..40f4a82 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-07-07" +channel = "nightly-2025-03-02" components = ["rustfmt", "clippy", "llvm-tools"] diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..f9df941 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,52 @@ +// Copyright 2025 OpenObserve Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +mod utils; + +pub async fn cli() -> Result { + let app = clap::Command::new("report-generator") + .version(crate::config::VERSION) + .about(clap::crate_description!()) + .subcommands(&[clap::Command::new("init-dir") + .about("init report-generator data dir") + .arg( + clap::Arg::new("path") + .short('p') + .long("path") + .help("init this path as data root dir"), + )]) + .get_matches(); + + if app.subcommand().is_none() { + return Ok(false); + } + + let (name, command) = app.subcommand().unwrap(); + if name == "init-dir" { + match command.get_one::("path") { + Some(path) => { + utils::set_permission(path, 0o777)?; + println!("init dir {} successfully", path); + } + None => { + return Err(anyhow::anyhow!("please set data path")); + } + } + return Ok(true); + } + + println!("command {name} execute successfully"); + Ok(true) +} diff --git a/src/cli/utils.rs b/src/cli/utils.rs new file mode 100644 index 0000000..dfbb270 --- /dev/null +++ b/src/cli/utils.rs @@ -0,0 +1,29 @@ +// Copyright 2025 OpenObserve Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#[cfg(unix)] +pub fn set_permission>(path: P, mode: u32) -> Result<(), std::io::Error> { + use std::os::unix::fs::PermissionsExt; + std::fs::create_dir_all(path.as_ref())?; + std::fs::set_permissions(path.as_ref(), std::fs::Permissions::from_mode(mode)) +} + +#[cfg(not(unix))] +pub fn set_permission>( + path: P, + _mode: u32, +) -> Result<(), std::io::Error> { + std::fs::create_dir_all(path.as_ref()) +} diff --git a/src/config.rs b/src/config.rs index 2093e21..97a44be 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Zinc Labs Inc. +// Copyright 2025 OpenObserve Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by @@ -29,123 +29,14 @@ use lettre::{ AsyncSmtpTransport, Tokio1Executor, }; use once_cell::sync::Lazy; -// use sysinfo::{DiskExt, SystemExt}; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub static CONFIG: Lazy = Lazy::new(init); static CHROME_LAUNCHER_OPTIONS: tokio::sync::OnceCell = tokio::sync::OnceCell::const_new(); -pub async fn get_chrome_launch_options() -> &'static BrowserConfig { - CHROME_LAUNCHER_OPTIONS - .get_or_init(init_chrome_launch_options) - .await -} - -async fn init_chrome_launch_options() -> BrowserConfig { - let mut browser_config = BrowserConfig::builder() - .window_size( - CONFIG.chrome.chrome_window_width, - CONFIG.chrome.chrome_window_height, - ) - .viewport(Viewport { - width: CONFIG.chrome.chrome_window_width, - height: CONFIG.chrome.chrome_window_height, - device_scale_factor: Some(1.0), - ..Viewport::default() - }); - - if CONFIG.chrome.chrome_with_head { - browser_config = browser_config.with_head(); - } - - if CONFIG.chrome.chrome_no_sandbox { - browser_config = browser_config.no_sandbox(); - } - - if CONFIG.chrome.chrome_disable_default_args { - browser_config = browser_config.disable_default_args(); - } - - if !CONFIG.chrome.chrome_additional_args.is_empty() { - browser_config = browser_config.args(CONFIG.chrome.chrome_additional_args.split(",")); - } - - if !CONFIG.chrome.chrome_path.is_empty() { - browser_config = browser_config.chrome_executable(CONFIG.chrome.chrome_path.as_str()); - } else { - let mut should_download = false; - - if !CONFIG.chrome.chrome_check_default { - should_download = true; - } else { - // Check if chrome is available on default paths - // 1. Check the CHROME env - // 2. Check usual chrome file names in user path - // 3. (Windows) Registry - // 4. (Windows & MacOS) Usual installations paths - if let Ok(exec_path) = default_executable(DetectionOptions::default()) { - browser_config = browser_config.chrome_executable(exec_path); - } else { - should_download = true; - } - } - if should_download { - // Download known good chrome version - let download_path = &CONFIG.chrome.chrome_download_path; - log::info!("fetching chrome at: {download_path}"); - tokio::fs::create_dir_all(download_path).await.unwrap(); - let fetcher = BrowserFetcher::new( - BrowserFetcherOptions::builder() - .with_path(download_path) - .build() - .unwrap(), - ); - - // Fetches the browser revision, either locally if it was previously - // installed or remotely. Returns error when the download/installation - // fails. Since it doesn't retry on network errors during download, - // if the installation fails, it might leave the cache in a bad state - // and it is advised to wipe it. - // Note: Does not work on LinuxArm platforms. - let info = fetcher - .fetch() - .await - .expect("chrome could not be downloaded"); - log::info!( - "chrome fetched at path {:#?}", - info.executable_path.as_path() - ); - browser_config = browser_config.chrome_executable(info.executable_path); - } - } - browser_config.build().unwrap() -} - -pub static SMTP_CLIENT: Lazy> = Lazy::new(|| { - let tls_parameters = TlsParameters::new(CONFIG.smtp.smtp_host.clone()).unwrap(); - let mut transport_builder = - AsyncSmtpTransport::::builder_dangerous(&CONFIG.smtp.smtp_host) - .port(CONFIG.smtp.smtp_port); - - let option = &CONFIG.smtp.smtp_encryption; - transport_builder = if option == "starttls" { - transport_builder.tls(Tls::Required(tls_parameters)) - } else if option == "ssltls" { - transport_builder.tls(Tls::Wrapper(tls_parameters)) - } else { - transport_builder - }; - - if !CONFIG.smtp.smtp_username.is_empty() && !CONFIG.smtp.smtp_password.is_empty() { - transport_builder = transport_builder.credentials(Credentials::new( - CONFIG.smtp.smtp_username.clone(), - CONFIG.smtp.smtp_password.clone(), - )); - } - transport_builder.build() -}); - #[derive(EnvConfig)] pub struct Config { pub auth: Auth, @@ -254,16 +145,117 @@ pub struct Common { pub local_mode: bool, } -// #[derive(EnvConfig)] -// pub struct Limit { -// // no need set by environment -// pub cpu_num: usize, -// pub mem_total: usize, -// pub disk_total: usize, -// pub disk_free: usize, -// } - pub fn init() -> Config { dotenv().ok(); Config::init().unwrap() } + +pub async fn get_chrome_launch_options() -> &'static BrowserConfig { + CHROME_LAUNCHER_OPTIONS + .get_or_init(init_chrome_launch_options) + .await +} + +async fn init_chrome_launch_options() -> BrowserConfig { + let mut browser_config = BrowserConfig::builder() + .window_size( + CONFIG.chrome.chrome_window_width, + CONFIG.chrome.chrome_window_height, + ) + .viewport(Viewport { + width: CONFIG.chrome.chrome_window_width, + height: CONFIG.chrome.chrome_window_height, + device_scale_factor: Some(1.0), + ..Viewport::default() + }); + + if CONFIG.chrome.chrome_with_head { + browser_config = browser_config.with_head(); + } + + if CONFIG.chrome.chrome_no_sandbox { + browser_config = browser_config.no_sandbox(); + } + + if CONFIG.chrome.chrome_disable_default_args { + browser_config = browser_config.disable_default_args(); + } + + if !CONFIG.chrome.chrome_additional_args.is_empty() { + browser_config = browser_config.args(CONFIG.chrome.chrome_additional_args.split(",")); + } + + if !CONFIG.chrome.chrome_path.is_empty() { + browser_config = browser_config.chrome_executable(CONFIG.chrome.chrome_path.as_str()); + } else { + let mut should_download = false; + + if !CONFIG.chrome.chrome_check_default { + should_download = true; + } else { + // Check if chrome is available on default paths + // 1. Check the CHROME env + // 2. Check usual chrome file names in user path + // 3. (Windows) Registry + // 4. (Windows & MacOS) Usual installations paths + if let Ok(exec_path) = default_executable(DetectionOptions::default()) { + browser_config = browser_config.chrome_executable(exec_path); + } else { + should_download = true; + } + } + if should_download { + // Download known good chrome version + let download_path = &CONFIG.chrome.chrome_download_path; + log::info!("fetching chrome at: {download_path}"); + tokio::fs::create_dir_all(download_path).await.unwrap(); + let fetcher = BrowserFetcher::new( + BrowserFetcherOptions::builder() + .with_path(download_path) + .build() + .unwrap(), + ); + + // Fetches the browser revision, either locally if it was previously + // installed or remotely. Returns error when the download/installation + // fails. Since it doesn't retry on network errors during download, + // if the installation fails, it might leave the cache in a bad state + // and it is advised to wipe it. + // Note: Does not work on LinuxArm platforms. + let info = fetcher + .fetch() + .await + .expect("chrome could not be downloaded"); + log::info!( + "chrome fetched at path {:#?}", + info.executable_path.as_path() + ); + browser_config = browser_config.chrome_executable(info.executable_path); + } + } + browser_config.build().unwrap() +} + +pub static SMTP_CLIENT: Lazy> = Lazy::new(|| { + let tls_parameters = TlsParameters::new(CONFIG.smtp.smtp_host.clone()).unwrap(); + let mut transport_builder = + AsyncSmtpTransport::::builder_dangerous(&CONFIG.smtp.smtp_host) + .port(CONFIG.smtp.smtp_port); + + let option = &CONFIG.smtp.smtp_encryption; + transport_builder = if option == "starttls" { + transport_builder.tls(Tls::Required(tls_parameters)) + } else if option == "ssltls" { + transport_builder.tls(Tls::Wrapper(tls_parameters)) + } else { + transport_builder + }; + + if !CONFIG.smtp.smtp_username.is_empty() && !CONFIG.smtp.smtp_password.is_empty() { + transport_builder = transport_builder.credentials(Credentials::new( + CONFIG.smtp.smtp_username.clone(), + CONFIG.smtp.smtp_password.clone(), + )); + } + transport_builder.build() +}); diff --git a/src/lib.rs b/src/lib.rs index 306f140..823a515 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Zinc Labs Inc. +// Copyright 2025 OpenObserve Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by @@ -13,6 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +pub mod cli; pub mod config; pub mod router; @@ -384,13 +385,11 @@ pub async fn generate_report( // Convert the page into pdf let pdf_data = match report_type { ReportType::PDF => { - let pdf = page - .pdf(PrintToPdfParams { - landscape: Some(true), - ..Default::default() - }) - .await?; - pdf + page.pdf(PrintToPdfParams { + landscape: Some(true), + ..Default::default() + }) + .await? } // No need to capture pdf when report type is cache ReportType::Cache => vec![], diff --git a/src/main.rs b/src/main.rs index 7007c56..6823088 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Zinc Labs Inc. +// Copyright 2025 OpenObserve Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by @@ -15,6 +15,7 @@ use actix_web::{dev::ServerHandle, middleware, web, App, HttpServer}; use o2_report_generator::{ + cli, config::{self, CONFIG}, router::{healthz, send_report}, }; @@ -27,6 +28,11 @@ async fn main() -> Result<(), anyhow::Error> { } env_logger::init(); + // cli mode + if cli::cli().await? { + return Ok(()); + } + // Locate or fetch chromium _ = config::get_chrome_launch_options().await; diff --git a/src/router.rs b/src/router.rs index e1cec96..7098551 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,3 +1,18 @@ +// Copyright 2025 OpenObserve Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + use actix_web::{get, http::StatusCode, put, web, HttpRequest, HttpResponse as ActixHttpResponse}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -110,9 +125,9 @@ pub async fn send_report( ) .await { - Ok(_) => Ok(ActixHttpResponse::Ok().json(HttpResponse::success(format!( - "report sent to emails successfully" - )))), + Ok(_) => Ok(ActixHttpResponse::Ok().json(HttpResponse::success( + "report sent to emails successfully".to_string(), + ))), Err(e) => { log::error!("Error sending emails to recepients: {e}"); Ok(ActixHttpResponse::InternalServerError()