diff --git a/CHANGELOG.md b/CHANGELOG.md index 620a764ad5..b60061656d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,9 @@ All notable changes to this project will be documented in this file. - Add `docs/cli-standard.md`, the contributor-facing summary of RFC-20 with the `location get` worked example and pointers to the shared validators, formatters, logging facade, and test helpers in `doublezero-cli-core`. - Update `CLAUDE.md` with a CLI-standard section pointing at RFC-20, the contributor doc, and the reference verb so future contributors land on the standard quickly. - Move the per-resource serviceability subcommand wrapper files (`accesspass`, `config`, `contributor`, `device`, `exchange`, `globalconfig`, `link`, `location`, `multicastgroup`, `permission`, `resource`, `tenant`, `user`) from `client/doublezero/src/cli/` into the module crate at `smartcontract/cli/src/cli/` per RFC-20 §Module contract item 2. Internal imports in the moved files switch from `doublezero_serviceability_cli::::*` to `crate::::*`. Binary import paths in `client/doublezero/src/{cli/command.rs,main.rs}` switch to `doublezero_serviceability_cli::cli::::*`. `cli/multicast.rs` stays in the binary because its `Subscribe`/`Unsubscribe`/`Publish`/`Unpublish` variants are async and depend on binary-local daemon-control infrastructure (`ServiceControllerImpl`, `crate::command::helpers::resolve_client_ip`); the binary now imports `MulticastGroupCliCommand` from the library. - - Introduce `doublezero_serviceability_cli::cli::ServiceabilityCommand`, the module crate's top-level subcommand enum + `async fn execute(ctx, client, out)` dispatcher per RFC-20 §Module contract item 2. Aggregates 17 serviceability variants (`Init`, `Migrate`, `Address`, `Balance`, `Config`, `GlobalConfig`, `Location`, `Exchange`, `Contributor`, `Permission`, `Tenant`, `Device`, `Link`, `AccessPass`, `User`, `Export`, `Keygen`, `Resource`) and owns the full dispatch tree currently inlined in `client/doublezero/src/main.rs`. Defined but not yet wired into the unified binary; the next PR adds `#[command(flatten)] Serviceability(ServiceabilityCommand)` to the binary's `Command` enum and collapses `main.rs` to a single dispatch arm. Binary `Command` enum and `main.rs` dispatch are unchanged; this is pure file relocation. + - Introduce `doublezero_serviceability_cli::cli::ServiceabilityCommand`, the module crate's top-level subcommand enum + `async fn execute(ctx, client, out)` dispatcher per RFC-20 §Module contract item 2. Aggregates 17 serviceability variants (`Init`, `Migrate`, `Address`, `Balance`, `Config`, `GlobalConfig`, `Location`, `Exchange`, `Contributor`, `Permission`, `Tenant`, `Device`, `Link`, `AccessPass`, `User`, `Export`, `Keygen`, `Resource`) and owns the full dispatch tree currently inlined in `client/doublezero/src/main.rs`. Defined but not yet wired into the unified binary; the next PR adds `#[command(flatten)] Serviceability(ServiceabilityCommand)` to the binary's `Command` enum and collapses `main.rs` to a single dispatch arm. + - Hoist `ServiceabilityCommand` into the unified `doublezero` binary via `#[command(flatten)]` on the binary's `Command` enum. Drops 17 explicit variants and collapses the `main.rs` dispatch match block from roughly 270 lines to one arm (`Command::Serviceability(cmd) => cmd.execute(&ctx, &client, &mut handle).await`). The binary retains daemon-control verbs (`Connect`, `Enable`, `Disable`, `Status`, `Disconnect`, `Latency`, `Routes`), raw-`DZClient` diagnostics (`Account`, `Accounts`, `Log`), the binary-local geolocation tree, `InitGeolocationConfig`, the multicast dispatch (whose `Subscribe`/`Unsubscribe`/`Publish`/`Unpublish` async arms depend on daemon-control infrastructure), and the `Completion` generator. User-facing `doublezero --help` is byte-identical to the pre-refactor output (29 visible top-level commands); no flag, name, or output change. Binary `Command` enum and `main.rs` dispatch are unchanged; this is pure file relocation. + - Move `MulticastGroupCommands` dispatch out of `client/doublezero/src/main.rs` and into a `pub fn execute(&client, &mut out)` method on the enum itself, defined next to the enum in `smartcontract/cli/src/cli/multicastgroup.rs`. Mirrors the per-resource dispatch pattern in `ServiceabilityCommand::execute` and finishes the flatten/collapse work for the one module-crate subtree reached through the binary's `MulticastCliCommand` wrapper (which has to stay binary-local because its `Subscribe`/`Unsubscribe`/`Publish`/`Unpublish` arms depend on `ServiceControllerImpl`). The binary's `Multicast` arm shrinks from a 5-level nested match (Allowlist → Publisher/Subscriber → Add/Remove/List, plus Create/Update/List/Get/Delete at the Group level) to `MulticastCommands::Group(args) => args.command.execute(&client, &mut handle)`. No flag, name, or user-facing output change. ## [v0.24.0](https://github.com/malbeclabs/doublezero/compare/client/v0.23.0...client/v0.24.0) - 2026-05-22 diff --git a/client/doublezero/src/cli/command.rs b/client/doublezero/src/cli/command.rs index 97332e210b..2e87776a55 100644 --- a/client/doublezero/src/cli/command.rs +++ b/client/doublezero/src/cli/command.rs @@ -1,3 +1,10 @@ +use clap::{Args, Subcommand}; +use clap_complete::Shell; +use doublezero_serviceability_cli::{ + account::GetAccountCliCommand, accounts::GetAccountsCliCommand, cli::ServiceabilityCommand, + geolocation::programconfig::init::InitProgramConfigCliCommand, logcommand::LogCliCommand, +}; + use crate::{ cli::{geolocation::GeolocationCliCommand, multicast::MulticastCliCommand}, command::{ @@ -6,124 +13,57 @@ use crate::{ latency::LatencyCliCommand, routes::RoutesCliCommand, status::StatusCliCommand, }, }; -use clap::{Args, Subcommand}; -use clap_complete::Shell; -use doublezero_serviceability_cli::{ - account::GetAccountCliCommand, - accounts::GetAccountsCliCommand, - address::AddressCliCommand, - balance::BalanceCliCommand, - cli::{ - accesspass::AccessPassCliCommand, config::ConfigCliCommand, - contributor::ContributorCliCommand, device::DeviceCliCommand, exchange::ExchangeCliCommand, - globalconfig::GlobalConfigCliCommand, link::LinkCliCommand, location::LocationCliCommand, - permission::PermissionCliCommand, resource::ResourceCliCommand, tenant::TenantCliCommand, - user::UserCliCommand, - }, - export::ExportCliCommand, - geolocation::programconfig::init::InitProgramConfigCliCommand, - init::InitCliCommand, - keygen::KeyGenCliCommand, - logcommand::LogCliCommand, - migrate::MigrateCliCommand, -}; +/// Top-level command tree for the unified `doublezero` binary. +/// +/// Per RFC-20 §Module contract item 2, the serviceability verbs live in +/// `doublezero_serviceability_cli::cli::ServiceabilityCommand` and are hoisted +/// to the top level here via `#[command(flatten)]`. The binary retains the +/// daemon-control verbs, the binary-local geolocation tree, the raw-`DZClient` +/// diagnostics (`Account`, `Accounts`, `Log`), the geolocation program-config +/// init, the binary-only `Completion` generator, and `Multicast` (whose +/// `Subscribe`/`Unsubscribe`/`Publish`/`Unpublish` arms depend on binary-local +/// daemon-control infrastructure). #[derive(Subcommand, Debug)] pub enum Command { - #[command(hide = true)] - Init(InitCliCommand), - #[command(hide = true)] - Migrate(MigrateCliCommand), - #[command(hide = true)] - InitGeolocationConfig(InitProgramConfigCliCommand), /// Connect your server to a doublezero device - #[command()] Connect(ProvisioningCliCommand), /// Enable the reconciler (start managing tunnels) - #[command()] Enable(EnableCliCommand), /// Disable the reconciler (tear down tunnels and stop managing them) - #[command()] Disable(DisableCliCommand), /// Get the status of your service - #[command()] Status(StatusCliCommand), /// Disconnect your server from the doublezero network - #[command()] Disconnect(DecommissioningCliCommand), /// Get device latencies - #[command()] Latency(LatencyCliCommand), /// View your installed routes - #[command()] Routes(RoutesCliCommand), - /// Get your public key - #[command()] - Address(AddressCliCommand), - /// Get your balance - #[command()] - Balance(BalanceCliCommand), - /// local configuration - #[command()] - Config(ConfigCliCommand), - /// Global network configuration - #[command()] - GlobalConfig(GlobalConfigCliCommand), + /// Get Account - #[command()] Account(GetAccountCliCommand), /// List Accounts #[command(hide = true)] Accounts(GetAccountsCliCommand), - /// Manage locations - #[command()] - Location(LocationCliCommand), - /// Manage exchanges - #[command()] - Exchange(ExchangeCliCommand), - /// Manage contributors - #[command()] - Contributor(ContributorCliCommand), - /// Manage permissions - #[clap()] - Permission(PermissionCliCommand), - /// Manage tenants - #[command()] - Tenant(TenantCliCommand), - /// Manage devices - #[command()] - Device(DeviceCliCommand), - /// Manage tunnels between devices - #[command()] - Link(LinkCliCommand), + /// Get logs + Log(LogCliCommand), - #[command()] - AccessPass(AccessPassCliCommand), + #[command(hide = true)] + InitGeolocationConfig(InitProgramConfigCliCommand), + /// Manage geolocation probes and users + Geolocation(GeolocationCliCommand), - /// Manage users - #[command()] - User(UserCliCommand), /// Manage multicast - #[command()] Multicast(MulticastCliCommand), - /// Manage geolocation probes and users - #[command()] - Geolocation(GeolocationCliCommand), - /// Export all data to files - #[command()] - Export(ExportCliCommand), - /// Create a new user identity - #[command()] - Keygen(KeyGenCliCommand), - /// Get logs - #[command()] - Log(LogCliCommand), + /// Generate shell completions - #[command()] Completion(CompletionCliCommand), - /// IP/ID Resource Management - #[command()] - Resource(ResourceCliCommand), + + /// Flattened serviceability variants (Device, Link, Location, User, ...). + /// Hoisted to top-level via `#[command(flatten)]`. + #[command(flatten)] + Serviceability(ServiceabilityCommand), } #[derive(Args, Debug, Clone)] diff --git a/client/doublezero/src/main.rs b/client/doublezero/src/main.rs index d6e8cf2a34..6130f3d975 100644 --- a/client/doublezero/src/main.rs +++ b/client/doublezero/src/main.rs @@ -13,27 +13,14 @@ use crate::cli::{ geolocation::{ probe::ProbeCommands, user::UserCommands as GeoUserCommands, GeolocationCommands, }, + multicast::MulticastCommands, }; use doublezero_cli_core::LogLevel; use doublezero_sdk::{geolocation::client::GeoClient, DZClient, ProgramVersion}; use doublezero_serviceability::pda::get_globalstate_pda; use doublezero_serviceability_cli::{ - checkversion::check_version, - cli::{ - config::ConfigCommands, - device::{DeviceCommands, InterfaceCommands}, - exchange::ExchangeCommands, - globalconfig::{ - AirdropCommands, AuthorityCommands, FeatureFlagsCommands, FoundationAllowlistCommands, - GlobalConfigCommands, QaAllowlistCommands, - }, - link::{LinkCommands, TopologyCommands}, - location::LocationCommands, - user::UserCommands, - }, - doublezerocommand::CliCommandImpl, - geoclicommand::GeoCliCommandImpl, - version::VersionCliCommand, + checkversion::check_version, cli::ServiceabilityCommand, doublezerocommand::CliCommandImpl, + geoclicommand::GeoCliCommandImpl, version::VersionCliCommand, }; use servicecontroller::ServiceControllerImpl; @@ -249,16 +236,18 @@ async fn main() -> eyre::Result<()> { } }; - // Skip version check for Status command to allow checking status of services when the program is running + // Skip version check for verbs that should always work even if the program is unavailable. let skip_version_check = matches!( &command, Command::Status(_) | Command::Enable(_) | Command::Disable(_) - | Command::Address(_) - | Command::Balance(_) - | Command::Export(_) | Command::Completion(_) + | Command::Serviceability( + ServiceabilityCommand::Address(_) + | ServiceabilityCommand::Balance(_) + | ServiceabilityCommand::Export(_), + ) ); if !app.no_version_warning && !skip_version_check { let stderr = std::io::stderr(); @@ -267,8 +256,7 @@ async fn main() -> eyre::Result<()> { } let res = match command { - Command::Address(args) => args.execute(&client, &mut handle), - Command::Balance(args) => args.execute(&client, &mut handle), + // Daemon-control verbs (binary-local) Command::Connect(args) => args.execute(&client).await, Command::Enable(args) => args.execute(&client).await, Command::Disable(args) => args.execute(&client).await, @@ -277,8 +265,12 @@ async fn main() -> eyre::Result<()> { Command::Latency(args) => args.execute(&client).await, Command::Routes(args) => args.execute(&client).await, - Command::Init(args) => args.execute(&client, &mut handle), - Command::Migrate(args) => args.execute(&client, &mut handle), + // Raw-DZClient diagnostic verbs + Command::Account(args) => args.execute(&dzclient, &mut handle), + Command::Accounts(args) => args.execute(&dzclient, &mut handle), + Command::Log(args) => args.execute(&dzclient, &mut handle), + + // Geolocation (binary-local; module crate deferred per RFC-20 scope) Command::InitGeolocationConfig(args) => { let geo_client = GeoClient::new(url.clone(), app.geo_program_id.clone(), app.keypair.clone())?; @@ -287,201 +279,6 @@ async fn main() -> eyre::Result<()> { let geo_cli = GeoCliCommandImpl::new(&geo_client, &dzclient, globalstate_pk); args.execute(&geo_cli, &mut handle) } - Command::Config(command) => match command.command { - ConfigCommands::Get(args) => args.execute(&client, &mut handle), - ConfigCommands::Set(args) => args.execute(&client, &mut handle), - }, - Command::GlobalConfig(command) => match command.command { - GlobalConfigCommands::Set(args) => args.execute(&client, &mut handle), - GlobalConfigCommands::Get(args) => args.execute(&client, &mut handle), - GlobalConfigCommands::Airdrop(command) => match command.command { - AirdropCommands::Set(args) => args.execute(&client, &mut handle), - AirdropCommands::Get(args) => args.execute(&client, &mut handle), - }, - GlobalConfigCommands::Authority(command) => match command.command { - AuthorityCommands::Set(args) => args.execute(&client, &mut handle), - AuthorityCommands::Get(args) => args.execute(&client, &mut handle), - }, - GlobalConfigCommands::Allowlist(command) => match command.command { - FoundationAllowlistCommands::List(args) => args.execute(&client, &mut handle), - FoundationAllowlistCommands::Add(args) => args.execute(&client, &mut handle), - FoundationAllowlistCommands::Remove(args) => args.execute(&client, &mut handle), - }, - GlobalConfigCommands::QaAllowlist(command) => match command.command { - QaAllowlistCommands::List(args) => args.execute(&client, &mut handle), - QaAllowlistCommands::Add(args) => args.execute(&client, &mut handle), - QaAllowlistCommands::Remove(args) => args.execute(&client, &mut handle), - }, - GlobalConfigCommands::SetVersion(args) => args.execute(&client, &mut handle), - GlobalConfigCommands::FeatureFlags(command) => match command.command { - FeatureFlagsCommands::Get(args) => args.execute(&client, &mut handle), - FeatureFlagsCommands::Set(args) => args.execute(&client, &mut handle), - }, - }, - Command::Account(args) => args.execute(&dzclient, &mut handle), - Command::Accounts(args) => args.execute(&dzclient, &mut handle), - Command::Location(command) => match command.command { - LocationCommands::Create(args) => args.execute(&client, &mut handle), - LocationCommands::Update(args) => args.execute(&client, &mut handle), - LocationCommands::List(args) => args.execute(&client, &mut handle), - LocationCommands::Get(args) => args.execute(&ctx, &client, &mut handle).await, - LocationCommands::Delete(args) => args.execute(&client, &mut handle), - }, - Command::Exchange(command) => match command.command { - ExchangeCommands::Create(args) => args.execute(&client, &mut handle), - ExchangeCommands::SetDevice(args) => args.execute(&client, &mut handle), - ExchangeCommands::Update(args) => args.execute(&client, &mut handle), - ExchangeCommands::List(args) => args.execute(&client, &mut handle), - ExchangeCommands::Get(args) => args.execute(&client, &mut handle), - ExchangeCommands::Delete(args) => args.execute(&client, &mut handle), - }, - Command::Contributor(command) => match command.command { - doublezero_serviceability_cli::cli::contributor::ContributorCommands::Create(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::contributor::ContributorCommands::Update(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::contributor::ContributorCommands::List(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::contributor::ContributorCommands::Get(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::contributor::ContributorCommands::Delete(args) => { - args.execute(&client, &mut handle) - } - }, - Command::Permission(command) => match command.command { - doublezero_serviceability_cli::cli::permission::PermissionCommands::Set(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::permission::PermissionCommands::Suspend(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::permission::PermissionCommands::Resume(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::permission::PermissionCommands::Delete(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::permission::PermissionCommands::Get(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::permission::PermissionCommands::List(args) => args.execute(&client, &mut handle), - }, - Command::Tenant(command) => match command.command { - doublezero_serviceability_cli::cli::tenant::TenantCommands::Create(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::tenant::TenantCommands::Update(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::tenant::TenantCommands::List(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::tenant::TenantCommands::Get(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::tenant::TenantCommands::Delete(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::tenant::TenantCommands::Administrator(command) => match command.command { - doublezero_serviceability_cli::cli::tenant::AdministratorCommands::Add(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::tenant::AdministratorCommands::Remove(args) => { - args.execute(&client, &mut handle) - } - }, - }, - Command::Device(command) => match command.command { - DeviceCommands::Create(args) => args.execute(&client, &mut handle), - DeviceCommands::Update(args) => args.execute(&client, &mut handle), - DeviceCommands::List(args) => args.execute(&client, &mut handle), - DeviceCommands::Get(args) => args.execute(&client, &mut handle), - DeviceCommands::Delete(args) => args.execute(&client, &mut handle), - DeviceCommands::Interface(command) => match command.command { - InterfaceCommands::Create(args) => args.execute(&client, &mut handle), - InterfaceCommands::Update(args) => args.execute(&client, &mut handle), - InterfaceCommands::List(args) => args.execute(&client, &mut handle), - InterfaceCommands::Get(args) => args.execute(&client, &mut handle), - InterfaceCommands::Delete(args) => args.execute(&client, &mut handle), - }, - DeviceCommands::SetHealth(args) => args.execute(&client, &mut handle), - }, - Command::Link(command) => match command.command { - LinkCommands::Create(args) => match args.command { - doublezero_serviceability_cli::cli::link::CreateLinkCommands::Wan(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::link::CreateLinkCommands::Dzx(args) => args.execute(&client, &mut handle), - }, - LinkCommands::Accept(args) => args.execute(&client, &mut handle), - LinkCommands::Update(args) => args.execute(&client, &mut handle), - LinkCommands::List(args) => args.execute(&client, &mut handle), - LinkCommands::Get(args) => args.execute(&client, &mut handle), - LinkCommands::Latency(args) => args.execute(&client, &mut handle), - LinkCommands::Delete(args) => args.execute(&client, &mut handle), - LinkCommands::SetHealth(args) => args.execute(&client, &mut handle), - LinkCommands::Topology(args) => match args.command { - TopologyCommands::Create(args) => args.execute(&client, &mut handle), - TopologyCommands::Delete(args) => args.execute(&client, &mut handle), - TopologyCommands::Clear(args) => args.execute(&client, &mut handle), - TopologyCommands::AssignNodeSegments(args) => args.execute(&client, &mut handle), - TopologyCommands::List(args) => args.execute(&client, &mut handle), - }, - }, - Command::AccessPass(command) => match command.command { - doublezero_serviceability_cli::cli::accesspass::AccessPassCommands::Set(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::accesspass::AccessPassCommands::Close(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::accesspass::AccessPassCommands::List(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::accesspass::AccessPassCommands::Get(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::accesspass::AccessPassCommands::UserBalances(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::accesspass::AccessPassCommands::Fund(args) => { - args.execute(&client, &mut handle, &mut std::io::stdin().lock()) - } - }, - Command::User(command) => match command.command { - UserCommands::Create(args) => args.execute(&client, &mut handle), - UserCommands::CreateSubscribe(args) => args.execute(&client, &mut handle), - UserCommands::Subscribe(args) => args.execute(&client, &mut handle), - UserCommands::Update(args) => args.execute(&client, &mut handle), - UserCommands::List(args) => args.execute(&client, &mut handle), - UserCommands::Get(args) => args.execute(&client, &mut handle), - UserCommands::Delete(args) => args.execute(&client, &mut handle), - UserCommands::RequestBan(args) => args.execute(&client, &mut handle), - }, - Command::Multicast(args) => match args.command { - cli::multicast::MulticastCommands::Group(args) => match args.command { - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupCommands::Allowlist(args) => { - match args.command { - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupAllowlistCommands::Publisher(args) => { - match args.command { - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupPubAllowlistCommands::List( - args, - ) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupPubAllowlistCommands::Add( - args, - ) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupPubAllowlistCommands::Remove( - args, - ) => args.execute(&client, &mut handle), - } - } - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupAllowlistCommands::Subscriber(args) => { - match args.command { - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupSubAllowlistCommands::List( - args, - ) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupSubAllowlistCommands::Add( - args, - ) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupSubAllowlistCommands::Remove( - args, - ) => args.execute(&client, &mut handle), - } - } - } - } - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupCommands::Create(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupCommands::Update(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupCommands::List(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupCommands::Get(args) => { - args.execute(&client, &mut handle) - } - doublezero_serviceability_cli::cli::multicastgroup::MulticastGroupCommands::Delete(args) => { - args.execute(&client, &mut handle) - } - }, - cli::multicast::MulticastCommands::Subscribe(args) => args.execute(&client).await, - cli::multicast::MulticastCommands::Unsubscribe(args) => args.execute(&client).await, - cli::multicast::MulticastCommands::Publish(args) => args.execute(&client).await, - cli::multicast::MulticastCommands::Unpublish(args) => args.execute(&client).await, - }, - Command::Geolocation(command) => { let geo_client = GeoClient::new(url.clone(), app.geo_program_id.clone(), app.keypair.clone())?; @@ -514,23 +311,27 @@ async fn main() -> eyre::Result<()> { } } - Command::Resource(command) => match command.command { - doublezero_serviceability_cli::cli::resource::ResourceCommands::Allocate(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::resource::ResourceCommands::Create(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::resource::ResourceCommands::Deallocate(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::resource::ResourceCommands::Get(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::resource::ResourceCommands::Close(args) => args.execute(&client, &mut handle), - doublezero_serviceability_cli::cli::resource::ResourceCommands::Verify(args) => args.execute(&client, &mut handle), + // Multicast: the `Group` subtree is module-crate business and dispatched by + // `MulticastGroupCommands::execute`; the daemon-coupled async verbs + // (Subscribe/Unsubscribe/Publish/Unpublish) stay binary-local because + // they depend on `ServiceControllerImpl` and `resolve_client_ip`. + Command::Multicast(args) => match args.command { + MulticastCommands::Group(args) => args.command.execute(&client, &mut handle), + MulticastCommands::Subscribe(args) => args.execute(&client).await, + MulticastCommands::Unsubscribe(args) => args.execute(&client).await, + MulticastCommands::Publish(args) => args.execute(&client).await, + MulticastCommands::Unpublish(args) => args.execute(&client).await, }, - Command::Export(args) => args.execute(&client, &mut handle), - Command::Keygen(args) => args.execute(&client, &mut handle), - Command::Log(args) => args.execute(&dzclient, &mut handle), + // Clap shell-completion generator (binary-local) Command::Completion(args) => { let mut cmd = App::command(); generate(args.shell, &mut cmd, "doublezero", &mut std::io::stdout()); Ok(()) } + + // Flattened serviceability module: single dispatch arm hoists 17 variants. + Command::Serviceability(cmd) => cmd.execute(&ctx, &client, &mut handle).await, }; match res { diff --git a/smartcontract/cli/src/cli/mod.rs b/smartcontract/cli/src/cli/mod.rs index 8662eb9027..3a70322f95 100644 --- a/smartcontract/cli/src/cli/mod.rs +++ b/smartcontract/cli/src/cli/mod.rs @@ -10,6 +10,8 @@ pub mod accesspass; pub mod command; pub mod config; + +pub use command::ServiceabilityCommand; pub mod contributor; pub mod device; pub mod exchange; diff --git a/smartcontract/cli/src/cli/multicastgroup.rs b/smartcontract/cli/src/cli/multicastgroup.rs index 8e77e66b51..8a160e4af4 100644 --- a/smartcontract/cli/src/cli/multicastgroup.rs +++ b/smartcontract/cli/src/cli/multicastgroup.rs @@ -1,23 +1,28 @@ +use std::io::Write; + use clap::{Args, Subcommand}; -use crate::multicastgroup::{ - allowlist::{ - publisher::{ - add::AddMulticastGroupPubAllowlistCliCommand, - list::ListMulticastGroupPubAllowlistCliCommand, - remove::RemoveMulticastGroupPubAllowlistCliCommand, - }, - subscriber::{ - add::AddMulticastGroupSubAllowlistCliCommand, - list::ListMulticastGroupSubAllowlistCliCommand, - remove::RemoveMulticastGroupSubAllowlistCliCommand, +use crate::{ + doublezerocommand::CliCommand, + multicastgroup::{ + allowlist::{ + publisher::{ + add::AddMulticastGroupPubAllowlistCliCommand, + list::ListMulticastGroupPubAllowlistCliCommand, + remove::RemoveMulticastGroupPubAllowlistCliCommand, + }, + subscriber::{ + add::AddMulticastGroupSubAllowlistCliCommand, + list::ListMulticastGroupSubAllowlistCliCommand, + remove::RemoveMulticastGroupSubAllowlistCliCommand, + }, }, + create::CreateMulticastGroupCliCommand, + delete::DeleteMulticastGroupCliCommand, + get::GetMulticastGroupCliCommand, + list::ListMulticastGroupCliCommand, + update::UpdateMulticastGroupCliCommand, }, - create::CreateMulticastGroupCliCommand, - delete::DeleteMulticastGroupCliCommand, - get::GetMulticastGroupCliCommand, - list::ListMulticastGroupCliCommand, - update::UpdateMulticastGroupCliCommand, }; #[derive(Args, Debug)] @@ -101,3 +106,33 @@ pub enum MulticastGroupSubAllowlistCommands { #[clap()] Remove(RemoveMulticastGroupSubAllowlistCliCommand), } + +impl MulticastGroupCommands { + /// Dispatch a multicast-group verb to its implementation. + /// + /// Lives in the module crate so the binary's `Multicast` arm only forwards + /// daemon-coupled async verbs (`Subscribe` / `Unsubscribe` / `Publish` / + /// `Unpublish`) and delegates the non-daemon group tree here. Mirrors the + /// per-resource dispatch pattern in `ServiceabilityCommand::execute`. + pub fn execute(self, client: &C, out: &mut W) -> eyre::Result<()> { + match self { + Self::Create(args) => args.execute(client, out), + Self::Update(args) => args.execute(client, out), + Self::List(args) => args.execute(client, out), + Self::Get(args) => args.execute(client, out), + Self::Delete(args) => args.execute(client, out), + Self::Allowlist(cmd) => match cmd.command { + MulticastGroupAllowlistCommands::Publisher(c) => match c.command { + MulticastGroupPubAllowlistCommands::List(args) => args.execute(client, out), + MulticastGroupPubAllowlistCommands::Add(args) => args.execute(client, out), + MulticastGroupPubAllowlistCommands::Remove(args) => args.execute(client, out), + }, + MulticastGroupAllowlistCommands::Subscriber(c) => match c.command { + MulticastGroupSubAllowlistCommands::List(args) => args.execute(client, out), + MulticastGroupSubAllowlistCommands::Add(args) => args.execute(client, out), + MulticastGroupSubAllowlistCommands::Remove(args) => args.execute(client, out), + }, + }, + } + } +}