mod bench;
mod cell;
mod config;
mod docker;
mod health;
mod output;
mod paths;
use anyhow::Result;
use clap::{Parser, Subcommand};
use std::env;
#[derive(Parser)]
#[command(
name = "knishio",
about = "KnishIO Validator Orchestration CLI",
version,
propagate_version = true
)]
struct Cli {
#[arg(long, global = true, default_value = "https://localhost:8080")]
url: String,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Start {
#[arg(long)]
build: bool,
#[arg(short, long)]
detach: bool,
},
Stop,
Destroy {
#[arg(long)]
volumes: bool,
},
Rebuild,
Logs {
#[arg(short, long)]
follow: bool,
#[arg(long)]
tail: Option<usize>,
},
Status,
Cell {
#[command(subcommand)]
command: CellCommands,
},
Bench {
#[command(subcommand)]
command: BenchCommands,
},
Health,
Ready,
Full,
Db,
}
#[derive(Subcommand)]
enum CellCommands {
Create {
slug: String,
#[arg(long)]
name: Option<String>,
#[arg(long, default_value = "active")]
status: String,
},
List,
Activate {
slug: String,
},
Pause {
slug: String,
},
Archive {
slug: String,
},
}
#[derive(Subcommand)]
enum BenchCommands {
Run {
#[arg(long, default_value_t = 50)]
identities: usize,
#[arg(long, default_value = "meta", value_delimiter = ',')]
types: Vec<String>,
#[arg(long, default_value_t = 100)]
metas_per_identity: usize,
#[arg(long, default_value_t = 10)]
transfers_per_identity: usize,
#[arg(long, default_value_t = 5)]
rules_per_identity: usize,
#[arg(long, default_value_t = 5)]
burns_per_identity: usize,
#[arg(long, default_value_t = 1_000_000.0)]
token_amount: f64,
#[arg(long, default_value = "https://localhost:8080")]
endpoint: String,
#[arg(long, default_value_t = 5)]
concurrency: usize,
#[arg(long)]
cell_slug: Option<String>,
},
Generate {
#[arg(long, default_value_t = 50)]
identities: usize,
#[arg(long, default_value = "meta", value_delimiter = ',')]
types: Vec<String>,
#[arg(long, default_value_t = 100)]
metas_per_identity: usize,
#[arg(long, default_value_t = 10)]
transfers_per_identity: usize,
#[arg(long, default_value_t = 5)]
rules_per_identity: usize,
#[arg(long, default_value_t = 5)]
burns_per_identity: usize,
#[arg(long, default_value_t = 1_000_000.0)]
token_amount: f64,
#[arg(short, long)]
output: String,
},
Execute {
plan: String,
#[arg(long, default_value = "https://localhost:8080")]
endpoint: String,
#[arg(long, default_value_t = 5)]
concurrency: usize,
#[arg(long)]
cell_slug: Option<String>,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let cwd = env::current_dir()?;
let cfg = config::Config::load(&cwd).with_url_override(&cli.url);
match cli.command {
Commands::Start { build, detach } => {
let compose = require_compose(&cwd, &cfg)?;
docker::start(&compose, build, detach).await?;
}
Commands::Stop => {
let compose = require_compose(&cwd, &cfg)?;
docker::stop(&compose).await?;
}
Commands::Destroy { volumes } => {
let compose = require_compose(&cwd, &cfg)?;
docker::destroy(&compose, volumes).await?;
}
Commands::Rebuild => {
let compose = require_compose(&cwd, &cfg)?;
docker::rebuild(&compose).await?;
}
Commands::Logs { follow, tail } => {
let compose = require_compose(&cwd, &cfg)?;
docker::logs(&compose, follow, tail).await?;
}
Commands::Status => {
let compose = require_compose(&cwd, &cfg)?;
docker::status(&compose).await?;
}
Commands::Cell { command } => match command {
CellCommands::Create { slug, name, status } => {
cell::create(&cfg, &slug, name.as_deref(), &status).await?;
}
CellCommands::List => {
cell::list(&cfg).await?;
}
CellCommands::Activate { slug } => {
cell::set_status(&cfg, &slug, "active").await?;
}
CellCommands::Pause { slug } => {
cell::set_status(&cfg, &slug, "paused").await?;
}
CellCommands::Archive { slug } => {
cell::set_status(&cfg, &slug, "archived").await?;
}
},
Commands::Bench { command } => {
let compose = require_compose(&cwd, &cfg)?;
let bench_bin = paths::find_bench_binary(&compose).ok_or_else(|| {
anyhow::anyhow!(
"knishio-bench binary not found. Build it first:\n \
cd servers/knishio-bench && cargo build --release"
)
})?;
match command {
BenchCommands::Run {
identities,
types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
endpoint,
concurrency,
cell_slug,
} => {
bench::run(
&bench_bin,
identities,
&types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
&endpoint,
concurrency,
cell_slug.as_deref(),
)
.await?;
}
BenchCommands::Generate {
identities,
types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
output: output_path,
} => {
bench::generate(
&bench_bin,
identities,
&types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
&output_path,
)
.await?;
}
BenchCommands::Execute {
plan,
endpoint,
concurrency,
cell_slug,
} => {
bench::execute(&bench_bin, &plan, &endpoint, concurrency, cell_slug.as_deref())
.await?;
}
}
}
Commands::Health => {
health::healthz(&cfg.validator.url, cfg.validator.insecure_tls).await?;
}
Commands::Ready => {
health::readyz(&cfg.validator.url, false, cfg.validator.insecure_tls).await?;
}
Commands::Full => {
health::readyz(&cfg.validator.url, true, cfg.validator.insecure_tls).await?;
}
Commands::Db => {
health::db_check(&cfg.validator.url, cfg.validator.insecure_tls).await?;
}
}
Ok(())
}
fn require_compose(cwd: &std::path::Path, cfg: &config::Config) -> Result<std::path::PathBuf> {
paths::find_compose_file(cwd, &cfg.docker.compose_file).ok_or_else(|| {
anyhow::anyhow!(
"Could not find {}.\n\
Run this command from inside the KnishIO project tree.",
cfg.docker.compose_file
)
})
}