feat: add pezkuwi-zombienet-cli crate

- New CLI binary for network orchestration
- Spawn command with native/docker/k8s providers
- Backward compatibility: parachains -> teyrchains alias
- Backward compatibility: onboard_as_parachain alias

Successfully tested with 21-validator mainnet simulation:
- 21/21 GRANDPA votes
- Block production and finality working
- Asset Hub and People Chain teyrchains running
This commit is contained in:
2026-01-21 00:25:18 +03:00
parent 25732b5d8c
commit 0b02590384
6 changed files with 193 additions and 11 deletions
+32
View File
@@ -0,0 +1,32 @@
[package]
name = "pezkuwi-zombienet-cli"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
publish = true
license.workspace = true
repository.workspace = true
description = "Pezkuwi Zombienet CLI - Network orchestration tool for Pezkuwi blockchain testing"
keywords = ["blockchain", "pezkuwi", "cli", "zombienet"]
[[bin]]
name = "pezkuwi-zombienet"
path = "src/main.rs"
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
# Zombienet SDK deps
pezkuwi-zombienet-sdk = { workspace = true }
configuration = { workspace = true }
orchestrator = { workspace = true }
provider = { workspace = true }
support = { workspace = true }
[features]
default = []
+139
View File
@@ -0,0 +1,139 @@
//! Pezkuwi Zombienet CLI - Network orchestration tool for Pezkuwi blockchain testing
//!
//! This CLI provides commands to spawn, manage, and test Pezkuwi blockchain networks
//! using the pezkuwi-zombienet-sdk.
use anyhow::{Context, Result};
use clap::{Parser, Subcommand, ValueEnum};
use pezkuwi_zombienet_sdk::{NetworkConfig, NetworkConfigExt};
use std::path::PathBuf;
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;
/// Pezkuwi Zombienet CLI - Network orchestration for Pezkuwi blockchain testing
#[derive(Parser, Debug)]
#[command(name = "pezkuwi-zombienet")]
#[command(author = "Kurdistan Tech Institute")]
#[command(version)]
#[command(about = "Network orchestration tool for Pezkuwi blockchain testing", long_about = None)]
struct Cli {
/// Enable verbose output
#[arg(short, long, global = true)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Spawn a new network from a configuration file
Spawn {
/// Path to the TOML configuration file
#[arg(value_name = "CONFIG_FILE")]
config: PathBuf,
/// Provider to use for spawning the network
#[arg(short, long, value_enum, default_value = "native")]
provider: Provider,
},
}
#[derive(ValueEnum, Clone, Debug)]
enum Provider {
/// Run nodes directly as native processes
Native,
/// Run nodes in Docker containers
Docker,
/// Run nodes in Kubernetes
K8s,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
// Set up tracing/logging
let level = if cli.verbose { Level::DEBUG } else { Level::INFO };
let subscriber = FmtSubscriber::builder()
.with_max_level(level)
.with_target(false)
.with_thread_ids(false)
.with_file(false)
.with_line_number(false)
.finish();
tracing::subscriber::set_global_default(subscriber)
.context("Failed to set tracing subscriber")?;
match cli.command {
Commands::Spawn { config, provider } => {
spawn_network(config, provider).await?;
}
}
Ok(())
}
async fn spawn_network(config_path: PathBuf, provider: Provider) -> Result<()> {
let config_str = config_path
.to_str()
.context("Invalid config path")?;
info!("Loading network configuration from: {}", config_str);
let network_config = NetworkConfig::load_from_toml(config_str)
.context("Failed to load network configuration")?;
info!("Network configuration loaded successfully");
info!("Relay chain: {}", network_config.relaychain().chain().as_str());
info!("Teyrchains: {}", network_config.teyrchains().len());
info!("Spawning network with provider: {:?}", provider);
let network = match provider {
Provider::Native => {
info!("Using native provider (direct process spawning)");
network_config.spawn_native().await
}
Provider::Docker => {
info!("Using Docker provider");
network_config.spawn_docker().await
}
Provider::K8s => {
info!("Using Kubernetes provider");
network_config.spawn_k8s().await
}
}
.context("Failed to spawn network")?;
info!("Network spawned successfully!");
// Print node information
for node in network.relaychain().nodes() {
info!(
"Relay node '{}' running at {}",
node.name(),
node.ws_uri()
);
}
for para in network.parachains() {
info!("Teyrchain ID {}", para.para_id());
for collator in para.collators() {
info!(
" Collator '{}' running at {}",
collator.name(),
collator.ws_uri()
);
}
}
info!("Press Ctrl+C to stop the network...");
// Keep the network running until interrupted
tokio::signal::ctrl_c().await?;
info!("Shutting down network...");
Ok(())
}
@@ -33,7 +33,7 @@ pub struct NetworkConfig {
#[serde(rename = "settings", default = "GlobalSettings::default")]
global_settings: GlobalSettings,
relaychain: Option<RelaychainConfig>,
#[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
#[serde(skip_serializing_if = "std::vec::Vec::is_empty", default, alias = "parachains")]
teyrchains: Vec<TeyrchainConfig>,
#[serde(skip_serializing_if = "std::vec::Vec::is_empty", default)]
hrmp_channels: Vec<HrmpChannelConfig>,
@@ -114,7 +114,7 @@ pub struct TeyrchainConfig {
chain: Option<Chain>,
#[serde(flatten)]
registration_strategy: Option<RegistrationStrategy>,
#[serde(skip_serializing_if = "super::utils::is_true", default = "default_as_true")]
#[serde(skip_serializing_if = "super::utils::is_true", default = "default_as_true", alias = "onboard_as_parachain")]
onboard_as_teyrchain: bool,
#[serde(rename = "balance", default = "default_initial_balance")]
initial_balance: U128,