Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doctor command for Health check #393

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/web5_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ tokio = { version = "1.38.0", features = ["full"] }
web5 = { path = "../web5" }
url = "2.5.2"
uuid = { workspace = true }
reqwest = "0.12.8"
colored = "2.1.0"
25 changes: 25 additions & 0 deletions crates/web5_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,28 @@ web5 vc create "alice" --portable-did $PORTABLE_DID

web5 vc verify eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6OXFnOGgxc3Jvd2hzZHNla3hwODk4eTU0MXhndGZ4Ym1ybW5oaGdzanlobXRtOHRjb253byMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJpZCI6InVybjp2Yzp1dWlkOjlkMDhhNjAzLWMyNTMtNGQyNC05M2MzLWIzYzAwMzg2NjM5MCIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmRodDo5cWc4aDFzcm93aHNkc2VreHA4OTh5NTQxeGd0ZnhibXJtbmhoZ3NqeWhtdG04dGNvbndvIiwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNi0yOFQxMzoxOTo1OS45OTY2MzMrMDA6MDAiLCJleHBpcmF0aW9uRGF0ZSI6bnVsbCwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJhbGljZSJ9fSwiaXNzIjoiZGlkOmRodDo5cWc4aDFzcm93aHNkc2VreHA4OTh5NTQxeGd0ZnhibXJtbmhoZ3NqeWhtdG04dGNvbndvIiwianRpIjoidXJuOnZjOnV1aWQ6OWQwOGE2MDMtYzI1My00ZDI0LTkzYzMtYjNjMDAzODY2MzkwIiwic3ViIjoiYWxpY2UiLCJuYmYiOjE3MTk1ODA3OTksImlhdCI6MTcxOTU4MDgwMH0.PJbb9EidggoqHL3IkfcglcTNzp_obBqbZjE0M4mL2XlecdLKNusZ3i4Hm0BtnzJ0ME7zYAvdIwg4shW4U884Bg
```


## Doctor Command

The `doctor` command is a feature to check the health of the cli. This command performs a series of diagnostic checks and reports the status of various functionalities. It is useful for ensuring that the system is functioning correctly and for troubleshooting potential issues.

### Usage

To use the `doctor` command, simply run:

```shell
#/bin/bash

web5 doctor
```

To apply for a specific check you can run:

```shell
#/bin/bash

web5 doctor --check cli-version
web5 doctor --check connectivity
web5 doctor --check env-vars
```
3 changes: 3 additions & 0 deletions crates/web5_cli/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ sudo mv /tmp/$FILENAME /usr/local/bin/web5

# Cleanup
rm /tmp/$FILENAME

# Running health check
/usr/local/bin/web5 doctor
218 changes: 218 additions & 0 deletions crates/web5_cli/src/doctor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use std::collections::HashMap;

use crate::CheckType;

pub struct HealthCheckState {
cli_version: Option<Option<String>>,
dependencies: Option<HashMap<String, bool>>,
env_vars: Option<HashMap<String, bool>>,
connectivity: Option<bool>,
basic_functionality: Option<bool>,
}

impl HealthCheckState {
fn new() -> Self {
Self {
cli_version: None,
dependencies: None,
env_vars: None,
connectivity: None,
basic_functionality: None,
}
}
}

fn check_cli_version() -> Option<Option<String>> {
Some(Some(env!("CARGO_PKG_VERSION").to_string())) // This will return the binary version.
}
Comment on lines +25 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn check_cli_version() -> Option<Option<String>> {
Some(Some(env!("CARGO_PKG_VERSION").to_string())) // This will return the binary version.
}
fn check_cli_version() -> Option<String> {
Some(env!("CARGO_PKG_VERSION").to_string())
}

can we remove the double Option and Some

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can but the first reason why I kept it over there is because let's say you want to run individual tests then in the print function , I needed a way to check if a user has asked for a certain check or not.

Let me re-think this in some other possible way.


fn check_dependencies() -> Option<HashMap<String, bool>> {
// TODO : Implement this function
// DOUBT : Are we expecting to check system dependencies or dependencies in Cargo.toml ?
// If cargo dependencies then how exactly shall we check the versions ?
None
}
Comment on lines +29 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we dont need this, everything should be in the cli binary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Will skip this then.


fn check_environment_variables() -> Option<HashMap<String, bool>> {
let mut env_vars = HashMap::new();
let vars = vec!["PORTABLE_DID"]; // Add env_vars that you want to include in health checkup

for var in vars {
let status = std::env::var(var).is_ok();
env_vars.insert(var.to_string(), status);
}

Some(env_vars)
}

async fn check_connectivity() -> Option<bool> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to check connectivity here?

for did web and did dht resolution ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes for web mainly
web5 did create web https://blackgirlbytes.com

let client = reqwest::Client::new();
Some(client.get("https://developer.tbd.website/projects/web5/").send().await.is_ok())
}

fn test_basic_functionality() -> Option<bool> {
// Supporting for actual binary
let web5_help = std::process::Command
::new("web5")
.arg("did")
.arg("create")
.arg("dht")
.output()
.is_ok();

// Supporting for cargo environment just for testing purposes.
let cargo_check = std::process::Command
::new("cargo")
.arg("run")
.arg("--")
.arg("did")
.arg("create")
.arg("dht")
.output()
.is_ok();

// If any one of the above commands is successful then return true.
Some(web5_help || cargo_check)
}

pub async fn run_health_checks(check: Option<CheckType>) -> HealthCheckState {
let mut state = HealthCheckState::new();

match check {
// Run specific checks
Some(CheckType::CliVersion) => {
state.cli_version = check_cli_version();
}
Some(CheckType::Dependencies) => {
state.dependencies = check_dependencies();
}
Some(CheckType::EnvVars) => {
state.env_vars = check_environment_variables();
}
Some(CheckType::Connectivity) => {
state.connectivity = check_connectivity().await;
}
Some(CheckType::BasicFunctionality) => {
state.basic_functionality = test_basic_functionality();
}
None => {
// Run all checks
state.cli_version = check_cli_version();
state.dependencies = check_dependencies();
state.env_vars = check_environment_variables();
state.connectivity = check_connectivity().await;
state.basic_functionality = test_basic_functionality();
}
}

state
}

use colored::*; // Add this line at the top of your file

pub fn print_health_check_results(state: &HealthCheckState) {
println!("{}", "Running Health Check for web5 CLI...".bold().blue());

// Handle CLI version
if let Some(cli_version) = &state.cli_version {
match cli_version {
Some(version) => println!("{} {}", "✔ CLI Version:".green(), version),
None =>
println!(
"{} {}",
"✖ CLI Version check failed.".red(),
"Please ensure the CLI is installed correctly.".yellow()
),
}
}

// Handle dependencies
if let Some(dependencies) = &state.dependencies {
for (dep, status) in dependencies {
println!(
"{} {}: {}",
if *status {
"✔".green()
} else {
"✖".red()
},
"Dependency".bold(),
dep
);
if !status {
println!(
"{} {}",
"Remediation:".yellow(),
format!("Please install or update the dependency: {}", dep).yellow()
);
}
}
}

// Handle environment variables
if let Some(env_vars) = &state.env_vars {
for (var, status) in env_vars {
println!(
"{} : {}",
if *status {
"✔ Environment Variable :".green()
} else {
"✖ Missing Environment Variable :".red()
},
if *status {
var.green()
} else {
var.red()
}
);
if !status {
println!("{}", format!("Please set the environment variable: {}", var).yellow());
// Example code to set the environment variable
println!("{}", format!("export {}=your_value", var).bright_yellow());
}
}
}

// Handle connectivity
if let Some(connectivity) = state.connectivity {
println!(
"{} {}",
if connectivity {
"✔ Connectivity:".green()
} else {
"✖ Connectivity:".red()
},
if connectivity {
"OK".green()
} else {
"FAILED".red()
}
);
if !connectivity {
println!("{}", "Please check your internet connection and try again.".yellow());
}
}

// Handle basic functionality
if let Some(basic_functionality) = state.basic_functionality {
println!(
"{} {}",
if basic_functionality {
"✔ Basic CLI Functionality:".green()
} else {
"✖ Basic CLI Functionality:".red()
},
if basic_functionality {
"OK".green()
} else {
"FAILED".red()
}
);
if !basic_functionality {
println!(
"{}",
"Might be a bug or your CLI have not been setup correctly. Please report on https://github.com/TBD54566975/web5-rs/issues ".yellow()
);
}
}
}
21 changes: 20 additions & 1 deletion crates/web5_cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod dids;
mod doctor;
mod test;
mod utils;
mod vcs;

use clap::{Parser, Subcommand};
use clap::{ Parser, Subcommand, ValueEnum };
use doctor::{ print_health_check_results, run_health_checks };

#[derive(Parser, Debug)]
#[command(
Expand All @@ -15,6 +17,15 @@ struct Cli {
command: Commands,
}

#[derive(Debug, Clone, ValueEnum)]
enum CheckType {
CliVersion,
Dependencies,
EnvVars,
Connectivity,
BasicFunctionality,
}

#[derive(Subcommand, Debug)]
enum Commands {
Did {
Expand All @@ -25,6 +36,10 @@ enum Commands {
#[command(subcommand)]
vc_command: vcs::Commands,
},
Doctor {
#[arg(long, value_enum)]
check: Option<CheckType>, // Optional argument for individual checks
},
}

#[tokio::main]
Expand All @@ -34,5 +49,9 @@ async fn main() {
match cli.command {
Commands::Did { did_command } => did_command.command().await,
Commands::Vc { vc_command } => vc_command.command().await,
Commands::Doctor { check } => {
let state = run_health_checks(check).await;
print_health_check_results(&state);
}
}
}
Loading