feat: show what ahab makes of this project
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use super::{Django, Link, Postgres};
|
use super::{Django, Link, Postgres, Store};
|
||||||
use clap::builder::styling::{AnsiColor, Effects, Styles};
|
use clap::builder::styling::{AnsiColor, Effects, Styles};
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
@@ -44,6 +44,12 @@ pub enum Commands {
|
|||||||
command: Link,
|
command: Link,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Show what ahab makes of this project
|
||||||
|
Status {
|
||||||
|
#[command(flatten)]
|
||||||
|
store: Store,
|
||||||
|
},
|
||||||
|
|
||||||
/// Print a shell completion script on stdout
|
/// Print a shell completion script on stdout
|
||||||
Completions {
|
Completions {
|
||||||
/// Shell to generate the script for
|
/// Shell to generate the script for
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ mod postgres;
|
|||||||
|
|
||||||
pub use ahab::{Ahab, Commands};
|
pub use ahab::{Ahab, Commands};
|
||||||
pub use django::Django;
|
pub use django::Django;
|
||||||
pub use link::Link;
|
pub use link::{Link, Store};
|
||||||
pub use postgres::{Format, Postgres};
|
pub use postgres::{Format, Postgres};
|
||||||
|
|||||||
@@ -182,6 +182,19 @@ fn stored_paths(repo: &Repo, dir: &Path) -> Result<Vec<(PathBuf, Stored)>> {
|
|||||||
Ok(found)
|
Ok(found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// what the store holds, without the git questions check asks
|
||||||
|
pub fn stored_summary(ctx: &Ctx, store: Option<&Path>) -> Result<(PathBuf, usize, usize)> {
|
||||||
|
let repo = Repo::discover(ctx, store)?;
|
||||||
|
let stored = stored_paths(&repo, &repo.store)?;
|
||||||
|
|
||||||
|
let linked = stored
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, state)| *state == Stored::Linked)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
Ok((repo.store, linked, stored.len() - linked))
|
||||||
|
}
|
||||||
|
|
||||||
// list what the store holds for this repository, the inverse of check
|
// list what the store holds for this repository, the inverse of check
|
||||||
pub fn list(ctx: &Ctx, store: Option<&Path>) -> Result<()> {
|
pub fn list(ctx: &Ctx, store: Option<&Path>) -> Result<()> {
|
||||||
let repo = Repo::discover(ctx, store)?;
|
let repo = Repo::discover(ctx, store)?;
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ pub mod completions;
|
|||||||
pub mod django;
|
pub mod django;
|
||||||
pub mod link;
|
pub mod link;
|
||||||
pub mod postgres;
|
pub mod postgres;
|
||||||
|
pub mod status;
|
||||||
|
|||||||
85
src/commands/status.rs
Normal file
85
src/commands/status.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::cmd::{Cmd, Ps};
|
||||||
|
use crate::commands::link;
|
||||||
|
use crate::ctx::Ctx;
|
||||||
|
use crate::project::Project;
|
||||||
|
|
||||||
|
// which services ahab picked, what it would talk to, what the store holds
|
||||||
|
pub fn status(ctx: &Ctx, store: Option<&Path>) -> Result<()> {
|
||||||
|
// a role it cannot pick out is worth reporting; a project it cannot read is not
|
||||||
|
let project = match Project::resolve(ctx) {
|
||||||
|
Ok(project) => project,
|
||||||
|
Err(e) => {
|
||||||
|
stored(ctx, store);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("services: {}", project.names().join(", "));
|
||||||
|
|
||||||
|
role(ctx, "django", project.django(), &project);
|
||||||
|
role(ctx, "postgres", project.postgres(), &project);
|
||||||
|
stored(ctx, store);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// what the store holds, and where to look for what it does not
|
||||||
|
fn stored(ctx: &Ctx, store: Option<&Path>) {
|
||||||
|
match link::stored_summary(ctx, store) {
|
||||||
|
Err(e) => println!("store: {e:#}"),
|
||||||
|
Ok((store, linked, other)) => {
|
||||||
|
println!("store: {}", store.display());
|
||||||
|
println!("\tlinked: {linked}");
|
||||||
|
|
||||||
|
if other > 0 {
|
||||||
|
println!("\tnot linked: {other} (see `ahab link list`)");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\t`ahab link check` lists what a sandbox can still read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// one detected service: the name, the image behind it, and whether it is up
|
||||||
|
fn role(ctx: &Ctx, role: &str, detected: Result<String>, project: &Project) {
|
||||||
|
let service = match detected {
|
||||||
|
Ok(service) => service,
|
||||||
|
Err(e) => {
|
||||||
|
println!("{role}: {e:#}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = project
|
||||||
|
.image(&service)
|
||||||
|
.unwrap_or("built from the project")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
println!("{role}: {service} ({image})");
|
||||||
|
|
||||||
|
match Ps::id_of(&service).capture(ctx) {
|
||||||
|
Ok(id) if id.trim().is_empty() => println!("\tcontainer: not running"),
|
||||||
|
Ok(id) => println!("\tcontainer: {}", short(id.trim())),
|
||||||
|
Err(e) => println!("\tcontainer: {e:#}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "postgres" {
|
||||||
|
let (user, database) = project.postgres_credentials(&service);
|
||||||
|
let source = |key: &str| match project.env(&service, key) {
|
||||||
|
Some(_) => "",
|
||||||
|
None => " (default, nothing in the environment)",
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("\tuser: {user}{}", source("POSTGRES_USER"));
|
||||||
|
println!("\tdatabase: {database}{}", source("POSTGRES_DB"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a container id is 64 characters and only the first few are ever typed
|
||||||
|
fn short(id: &str) -> String {
|
||||||
|
id.chars().take(12).collect()
|
||||||
|
}
|
||||||
@@ -97,6 +97,11 @@ fn run(ctx: &Ctx, command: cli::Commands) -> Result<ExitCode> {
|
|||||||
store.root.as_deref(),
|
store.root.as_deref(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
cli::Commands::Status { store } => {
|
||||||
|
commands::status::status(ctx, store.root.as_deref())?;
|
||||||
|
|
||||||
|
Ok(done)
|
||||||
|
}
|
||||||
cli::Commands::Completions { shell } => {
|
cli::Commands::Completions { shell } => {
|
||||||
commands::completions::completions(shell)?;
|
commands::completions::completions(shell)?;
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ impl Project {
|
|||||||
(user, database)
|
(user, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn names(&self) -> Vec<&str> {
|
pub fn names(&self) -> Vec<&str> {
|
||||||
let mut names: Vec<&str> = self
|
let mut names: Vec<&str> = self
|
||||||
.services
|
.services
|
||||||
.as_object()
|
.as_object()
|
||||||
@@ -102,6 +102,16 @@ impl Project {
|
|||||||
names
|
names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the image a service runs, which a service that builds its own does not have
|
||||||
|
pub fn image(&self, service: &str) -> Option<&str> {
|
||||||
|
self.services[service].get("image")?.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
// whether a value was set in the project or is ahab's own fallback
|
||||||
|
pub fn env(&self, service: &str, key: &str) -> Option<String> {
|
||||||
|
env_var(&self.services[service], key)
|
||||||
|
}
|
||||||
|
|
||||||
fn publishes_ports(&self, service: &str) -> bool {
|
fn publishes_ports(&self, service: &str) -> bool {
|
||||||
self.services[service]
|
self.services[service]
|
||||||
.get("ports")
|
.get("ports")
|
||||||
|
|||||||
Reference in New Issue
Block a user