use clap::{Args, Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
name = "xbp",
version,
about = "Deploy, operate, and debug services with one CLI.",
long_about = "XBP is an operations-first CLI for deployments, diagnostics, service orchestration,\nnetwork controls, and runtime observability.",
disable_help_subcommand = false,
next_line_help = true,
help_template = "{before-help}{name} {version}\n{about-with-newline}\
{usage-heading} {usage}\n\n\
{all-args}\
{after-help}",
after_help = "Quick start:\n xbp diag\n xbp services\n xbp service start <name>\n xbp api install --port 8080\n\nUse `xbp <command> -h` for command-specific examples."
)]
pub struct Cli {
#[arg(long, global = true, help = "Enable verbose debugging output")]
pub debug: bool,
#[arg(short = 'l', help = "List pm2 processes")]
pub list: bool,
#[arg(short = 'p', long = "port", help = "Filter by port number")]
pub port: Option<u16>,
#[arg(long, help = "Open logs directory")]
pub logs: bool,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(about = "Inspect or manage listening ports")]
Ports(PortsCmd),
#[command(about = "Initialize an XBP project in the current directory")]
Init,
#[command(about = "Install common dependencies for host setup")]
Setup,
#[command(about = "Redeploy one service or the entire project")]
Redeploy {
#[arg(
help = "Service name to redeploy (optional, uses legacy redeploy.sh if not provided)"
)]
service_name: Option<String>,
},
#[command(about = "Run the legacy remote redeploy workflow over SSH")]
RedeployV2(RedeployV2Cmd),
#[command(about = "Inspect project/global config and manage provider keys")]
Config(ConfigCmd),
#[command(about = "Install one of XBP's supported packages/scripts")]
Install { package: String },
#[command(about = "Tail local or remote logs")]
Logs(LogsCmd),
#[command(about = "List PM2 processes")]
List,
#[command(about = "Fetch an HTTP endpoint with sane defaults")]
Curl(CurlCmd),
#[command(about = "List configured services from project config")]
Services,
#[command(about = "Run service-level commands (build/install/start/dev)")]
Service {
#[arg(help = "Command to run: build, install, start, dev, or --help")]
command: Option<String>,
#[arg(help = "Service name")]
service_name: Option<String>,
},
#[command(about = "Manage NGINX site configs and upstream mappings")]
Nginx(NginxCmd),
#[command(about = "Manage host network configuration and floating IPs")]
Network(NetworkCmd),
#[command(about = "Run full system diagnostics and readiness checks")]
Diag(DiagCmd),
#[command(about = "Run health-check monitoring commands")]
Monitor(MonitorCmd),
#[command(about = "Capture a PM2 snapshot for later restore")]
Snapshot,
#[command(about = "Restore PM2 state from dump or latest snapshot")]
Resurrect,
#[command(about = "Stop a PM2 process by name or stop all")]
Stop {
#[arg(help = "PM2 process name or 'all' (default: all)")]
target: Option<String>,
},
#[command(about = "Flush PM2 logs globally or for a specific process")]
Flush {
#[arg(help = "Optional PM2 process name")]
target: Option<String>,
},
#[command(about = "Run login flow against configured XBP API")]
Login,
#[command(about = "Inspect, reconcile, or bump project versions")]
Version(VersionCmd),
#[command(about = "Show PM2 environment by name or numeric id")]
Env {
#[arg(help = "PM2 process name or id")]
target: String,
},
#[command(about = "Tail app logs or Kafka logs")]
Tail(TailCmd),
#[command(about = "Start a binary/process under PM2")]
Start {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
#[command(about = "Generate helper artifacts such as systemd units")]
Generate(GenerateCmd),
#[cfg(feature = "secrets")]
#[command(about = "Manage env vars and GitHub repository secrets (feature-gated)")]
Secrets(SecretsCmd),
#[command(
about = "Generate 'what did I get done' Markdown report from git commits across repos"
)]
Done(DoneCmd),
#[cfg(feature = "kubernetes")]
#[command(about = "Experimental Kubernetes cluster manager (feature-gated)")]
Kubernetes(KubernetesCmd),
#[cfg(feature = "nordvpn")]
#[command(about = "NordVPN meshnet setup and passthrough (feature-gated)")]
Nordvpn(NordvpnCmd),
#[cfg(feature = "monitoring")]
Monitoring(MonitoringCmd),
#[command(about = "Manage the XBP API server")]
Api(ApiCmd),
#[cfg(feature = "docker")]
#[command(about = "Pass-through wrapper around the Docker CLI")]
Docker(DockerCmd),
}
pub fn command_label(command: &Commands) -> &'static str {
match command {
Commands::Ports(_) => "ports",
Commands::Init => "init",
Commands::Setup => "setup",
Commands::Redeploy { .. } => "redeploy",
Commands::RedeployV2(_) => "redeploy-v2",
Commands::Config(_) => "config",
Commands::Install { .. } => "install",
Commands::Logs(_) => "logs",
Commands::List => "list",
Commands::Curl(_) => "curl",
Commands::Services => "services",
Commands::Service { .. } => "service",
Commands::Nginx(_) => "nginx",
Commands::Network(_) => "network",
Commands::Diag(_) => "diag",
Commands::Monitor(_) => "monitor",
Commands::Snapshot => "snapshot",
Commands::Resurrect => "resurrect",
Commands::Stop { .. } => "stop",
Commands::Flush { .. } => "flush",
Commands::Login => "login",
Commands::Version(_) => "version",
Commands::Env { .. } => "env",
Commands::Tail(_) => "tail",
Commands::Start { .. } => "start",
Commands::Generate(_) => "generate",
#[cfg(feature = "secrets")]
Commands::Secrets(_) => "secrets",
Commands::Done(_) => "done",
#[cfg(feature = "kubernetes")]
Commands::Kubernetes(_) => "kubernetes",
#[cfg(feature = "nordvpn")]
Commands::Nordvpn(_) => "nordvpn",
#[cfg(feature = "monitoring")]
Commands::Monitoring(_) => "monitoring",
Commands::Api(_) => "api",
#[cfg(feature = "docker")]
Commands::Docker(_) => "docker",
}
}
#[derive(Args, Debug)]
pub struct PortsCmd {
#[arg(short = 'p', long = "port")]
pub port: Option<u16>,
#[arg(long = "kill")]
pub kill: bool,
#[arg(short = 'n', long = "nginx")]
pub nginx: bool,
#[arg(
long = "full",
help = "Show reconciled active, NGINX, and XBP project ports"
)]
pub full: bool,
#[arg(
long = "no-local",
help = "Exclude connections where LocalAddr equals RemoteAddr"
)]
pub no_local: bool,
#[arg(
long = "exposure",
help = "Diagnose external exposure per port (binding + firewall layer)"
)]
pub exposure: bool,
}
#[derive(Args, Debug)]
pub struct ConfigCmd {
#[arg(
long,
help = "Show the current project config instead of opening global XBP paths"
)]
pub project: bool,
#[arg(long, help = "Print global XBP paths without opening them")]
pub no_open: bool,
#[command(subcommand)]
pub provider: Option<ConfigProviderCmd>,
}
#[derive(Subcommand, Debug)]
pub enum ConfigProviderCmd {
#[command(about = "Manage the OpenRouter API key used by AI-enabled commands")]
Openrouter(ConfigSecretCmd),
#[command(about = "Manage the GitHub OAuth2 token used for release automation")]
Github(ConfigSecretCmd),
}
#[derive(Args, Debug)]
pub struct ConfigSecretCmd {
#[command(subcommand)]
pub action: ConfigSecretAction,
}
#[derive(Subcommand, Debug)]
pub enum ConfigSecretAction {
#[command(about = "Set provider key (omit value to enter it securely)")]
SetKey {
#[arg(help = "Provider key/token value")]
key: Option<String>,
},
#[command(about = "Delete the stored provider key")]
DeleteKey,
#[command(about = "Show whether a key is configured (masked by default)")]
Show {
#[arg(long, help = "Print full key/token value (not masked)")]
raw: bool,
},
}
#[derive(Args, Debug)]
pub struct CurlCmd {
#[arg(help = "URL or domain to fetch, e.g. example.com or https://example.com/api")]
pub url: Option<String>,
#[arg(long, help = "Disable the default 15 second timeout")]
pub no_timeout: bool,
}
#[derive(Args, Debug)]
#[command(subcommand_precedence_over_arg = true)]
pub struct VersionCmd {
#[arg(
help = "Show versions, bump with major/minor/patch, or set an explicit version like 1.2.3"
)]
pub target: Option<String>,
#[arg(long, help = "Show normalized git tags from `git tag --list`")]
pub git: bool,
#[command(subcommand)]
pub command: Option<VersionSubCommand>,
}
#[derive(Subcommand, Debug)]
pub enum VersionSubCommand {
#[command(about = "Create and push a git tag for this version, then create a GitHub release")]
Release(VersionReleaseCmd),
}
#[derive(Args, Debug)]
pub struct VersionReleaseCmd {
#[arg(
long,
help = "Release this version instead of auto-detecting from tracked files"
)]
pub version: Option<String>,
#[arg(
long,
help = "Allow releasing with uncommitted changes in the working tree"
)]
pub allow_dirty: bool,
#[arg(long, help = "Release title (defaults to v<version>)")]
pub title: Option<String>,
#[arg(long, help = "Release notes body (Markdown)")]
pub notes: Option<String>,
#[arg(long, help = "Read release notes body from a file")]
pub notes_file: Option<PathBuf>,
#[arg(long, help = "Create as draft release")]
pub draft: bool,
#[arg(long, help = "Mark release as pre-release")]
pub prerelease: bool,
}
#[derive(Args, Debug)]
pub struct RedeployV2Cmd {
#[arg(short = 'p', long = "password")]
pub password: Option<String>,
#[arg(short = 'u', long = "username")]
pub username: Option<String>,
#[arg(short = 'h', long = "host")]
pub host: Option<String>,
#[arg(short = 'd', long = "project-dir")]
pub project_dir: Option<String>,
}
#[derive(Args, Debug)]
pub struct LogsCmd {
#[arg()]
pub project: Option<String>,
#[arg(long = "ssh-host", help = "SSH host to stream logs from")]
pub ssh_host: Option<String>,
#[arg(long = "ssh-username", help = "SSH username for remote host")]
pub ssh_username: Option<String>,
#[arg(long = "ssh-password", help = "SSH password for remote host")]
pub ssh_password: Option<String>,
}
#[derive(Args, Debug)]
pub struct NginxCmd {
#[command(subcommand)]
pub command: NginxSubCommand,
}
#[derive(Args, Debug)]
pub struct NetworkCmd {
#[command(subcommand)]
pub command: NetworkSubCommand,
}
#[derive(Subcommand, Debug)]
pub enum NetworkSubCommand {
#[command(about = "Manage persistent floating IP configuration")]
FloatingIp(NetworkFloatingIpCmd),
#[command(about = "Inspect discovered network configuration sources")]
Config(NetworkConfigCmd),
}
#[derive(Args, Debug)]
pub struct NetworkFloatingIpCmd {
#[command(subcommand)]
pub command: NetworkFloatingIpSubCommand,
}
#[derive(Subcommand, Debug)]
pub enum NetworkFloatingIpSubCommand {
#[command(about = "Add a persistent floating IP entry to detected network backend")]
Add {
#[arg(long, help = "Floating IP address (IPv4 or IPv6)")]
ip: String,
#[arg(long, help = "CIDR suffix (defaults: IPv4=32, IPv6=64)")]
cidr: Option<u8>,
#[arg(long, help = "Network interface override (auto-detected when omitted)")]
interface: Option<String>,
#[arg(long, help = "Optional label for backend metadata/file naming")]
label: Option<String>,
#[arg(long, help = "Apply network changes after writing config")]
apply: bool,
#[arg(long, help = "Preview computed changes without writing files")]
dry_run: bool,
},
#[command(about = "List floating IPs from runtime and persisted network config")]
List {
#[arg(long, help = "Emit JSON output")]
json: bool,
},
}
#[derive(Args, Debug)]
pub struct NetworkConfigCmd {
#[command(subcommand)]
pub command: NetworkConfigSubCommand,
}
#[derive(Subcommand, Debug)]
pub enum NetworkConfigSubCommand {
#[command(about = "List detected backend and configuration source files")]
List {
#[arg(long, help = "Emit JSON output")]
json: bool,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub enum NginxDnsMode {
Manual,
Plugin,
}
#[derive(Subcommand, Debug)]
pub enum NginxSubCommand {
#[command(
about = "Provision an HTTPS NGINX reverse proxy with Certbot",
long_about = "Provision an NGINX reverse proxy, issue or reuse Let's Encrypt certificates,\n\
and write final HTTP->HTTPS redirect + TLS proxy config.\n\
\n\
Wildcard domains (for example *.example.com) require DNS-01 mode.\n\
Use --dns-mode manual for interactive TXT record prompts, or --dns-mode plugin\n\
with --dns-plugin and --dns-creds for non-interactive provider automation."
)]
Setup {
#[arg(short, long, help = "Domain name (supports wildcard: *.example.com)")]
domain: String,
#[arg(short, long, help = "Port to proxy to")]
port: u16,
#[arg(
short,
long,
help = "Email used for Let's Encrypt account registration"
)]
email: String,
#[arg(
long,
value_enum,
default_value_t = NginxDnsMode::Manual,
help = "DNS challenge mode for wildcard certificates: manual or plugin"
)]
dns_mode: NginxDnsMode,
#[arg(
long,
help = "Certbot DNS plugin name for --dns-mode plugin (for example: cloudflare)"
)]
dns_plugin: Option<String>,
#[arg(
long,
help = "Path to DNS plugin credentials file for --dns-mode plugin"
)]
dns_creds: Option<PathBuf>,
#[arg(
long,
default_value_t = true,
action = clap::ArgAction::Set,
value_parser = clap::builder::BoolishValueParser::new(),
help = "For wildcard domains, also request the base domain certificate (true|false)"
)]
include_base: bool,
},
#[command(about = "List discovered NGINX sites with listen/upstream ports")]
List,
#[command(about = "Show full NGINX config for one domain or all domains")]
Show {
#[arg(help = "Optional domain name to inspect")]
domain: Option<String>,
},
#[command(about = "Open an NGINX site config in your configured editor")]
Edit {
#[arg(help = "Domain name to edit")]
domain: String,
},
#[command(about = "Update upstream port for an existing NGINX site")]
Update {
#[arg(short, long, help = "Domain name to update")]
domain: String,
#[arg(short, long, help = "New port to proxy to")]
port: u16,
},
}
#[derive(Args, Debug)]
pub struct DiagCmd {
#[arg(long, help = "Check Nginx configuration")]
pub nginx: bool,
#[arg(long, help = "Check specific ports (comma-separated)")]
pub ports: Option<String>,
#[arg(long, help = "Skip internet speed test")]
pub no_speed_test: bool,
#[arg(
long,
help = "Path to docker compose file to validate (defaults to docker-compose.yml/compose.yml)"
)]
pub compose_file: Option<String>,
}
#[derive(Args, Debug)]
pub struct MonitorCmd {
#[command(subcommand)]
pub command: Option<MonitorSubCommand>,
}
#[derive(Subcommand, Debug)]
pub enum MonitorSubCommand {
Check,
Start,
}
#[cfg(feature = "monitoring")]
#[derive(Args, Debug)]
pub struct MonitoringCmd {
#[command(subcommand)]
pub command: MonitoringSubCommand,
}
#[cfg(feature = "monitoring")]
#[derive(Subcommand, Debug)]
pub enum MonitoringSubCommand {
Serve {
#[arg(
short,
long,
default_value = "prodzilla.yml",
help = "Monitoring config file"
)]
file: String,
},
RunOnce {
#[arg(
short,
long,
default_value = "prodzilla.yml",
help = "Monitoring config file"
)]
file: String,
#[arg(long, help = "Run probes only")]
probes_only: bool,
#[arg(long, help = "Run stories only")]
stories_only: bool,
},
List {
#[arg(
short,
long,
default_value = "prodzilla.yml",
help = "Monitoring config file"
)]
file: String,
},
}
#[derive(Args, Debug)]
pub struct ApiCmd {
#[command(subcommand)]
pub command: ApiSubCommand,
}
#[cfg(feature = "docker")]
#[derive(Args, Debug)]
pub struct DockerCmd {
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
help = "Arguments to pass directly to the Docker CLI (default: --help)"
)]
pub args: Vec<String>,
}
#[derive(Subcommand, Debug)]
pub enum ApiSubCommand {
Install {
#[arg(long, default_value_t = 8080, help = "Port to expose the API on")]
port: u16,
},
}
#[derive(Args, Debug)]
pub struct TailCmd {
#[arg(long, help = "Tail Kafka topic instead of log files")]
pub kafka: bool,
#[arg(long, help = "Ship logs to Kafka")]
pub ship: bool,
}
#[derive(Args, Debug)]
pub struct GenerateCmd {
#[command(subcommand)]
pub command: GenerateSubCommand,
}
#[derive(Subcommand, Debug)]
pub enum GenerateSubCommand {
Systemd(GenerateSystemdCmd),
}
#[cfg(feature = "secrets")]
#[derive(Args, Debug)]
pub struct SecretsCmd {
#[arg(long, help = "GitHub repository override (owner/repo)")]
pub repo: Option<String>,
#[arg(long, help = "GitHub token to use (repo scope for private repos)")]
pub token: Option<String>,
#[command(subcommand)]
pub command: Option<SecretsSubCommand>,
}
#[cfg(feature = "secrets")]
#[derive(Subcommand, Debug)]
pub enum SecretsSubCommand {
List(ListCmd),
Push(PushCmd),
Pull(PullCmd),
GenerateDefault(GenerateDefaultCmd),
GenerateExample(GenerateExampleCmd),
Diff,
Verify,
Doctor,
#[command(name = "usage")]
Usage,
}
#[cfg(feature = "secrets")]
#[derive(Args, Debug)]
pub struct ListCmd {
#[arg(long, help = "Env file to list (.env.local, .env, .env.default)")]
pub file: Option<String>,
#[arg(long, help = "Output format: plain (default) or json")]
pub format: Option<String>,
}
#[cfg(feature = "secrets")]
#[derive(Args, Debug)]
pub struct PushCmd {
#[arg(long, help = "Path to env file (default: .env.local/.env)")]
pub file: Option<String>,
#[arg(long, help = "Force overwrite existing repository variables")]
pub force: bool,
#[arg(long, help = "Show what would be pushed without making changes")]
pub dry_run: bool,
}
#[cfg(feature = "secrets")]
#[derive(Args, Debug)]
pub struct PullCmd {
#[arg(long, help = "Output file path (default: .env.local)")]
pub output: Option<String>,
}
#[cfg(feature = "secrets")]
#[derive(Args, Debug)]
pub struct GenerateDefaultCmd {
#[arg(long, help = "Output file path (default: .env.default)")]
pub output: Option<String>,
}
#[cfg(feature = "secrets")]
#[derive(Args, Debug)]
pub struct GenerateExampleCmd {
#[arg(long, help = "Output file path (default: .env.example)")]
pub output: Option<String>,
#[arg(long, help = "Remove keys from .env.local not in .env.example")]
pub clean: bool,
#[arg(long, help = "Only include vars matching prefix (repeatable)")]
pub include_prefix: Vec<String>,
#[arg(long, help = "Exclude vars matching prefix (repeatable)")]
pub exclude_prefix: Vec<String>,
}
#[derive(Args, Debug)]
pub struct GenerateSystemdCmd {
#[arg(
long,
default_value = "/etc/systemd/system",
help = "Directory where the systemd units are written"
)]
pub output_dir: PathBuf,
#[arg(long, help = "Only generate the unit for this service name")]
pub service: Option<String>,
#[arg(
long,
default_value_t = true,
help = "Also generate the xbp-api systemd unit alongside project/services"
)]
pub api: bool,
}
#[derive(Args, Debug)]
pub struct DoneCmd {
#[arg(long, help = "Root directory under which to discover git repos")]
pub root: Option<std::path::PathBuf>,
#[arg(
long,
default_value = "24 hours ago",
help = "Git --since value (e.g. '7 days ago')"
)]
pub since: String,
#[arg(short, long, help = "Output Markdown file path")]
pub output: Option<std::path::PathBuf>,
#[arg(long, help = "Skip AI summarization (OpenRouter)")]
pub no_ai: bool,
#[arg(short, long, help = "Discover repos recursively")]
pub recursive: bool,
#[arg(long, help = "Exclude repo by name (repeatable)")]
pub exclude: Vec<String>,
}
#[cfg(feature = "nordvpn")]
#[derive(Args, Debug)]
pub struct NordvpnCmd {
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
help = "Subcommand or args to pass to nordvpn (e.g. setup, meshnet peer list)"
)]
pub args: Vec<String>,
}
#[cfg(feature = "kubernetes")]
#[derive(Args, Debug)]
pub struct KubernetesCmd {
#[command(subcommand)]
pub command: KubernetesSubCommand,
}
#[cfg(feature = "kubernetes")]
#[derive(Args, Debug)]
pub struct KubernetesAddonCmd {
#[command(subcommand)]
pub command: KubernetesAddonSubCommand,
}
#[cfg(feature = "kubernetes")]
#[derive(Subcommand, Debug)]
pub enum KubernetesAddonSubCommand {
List,
Enable {
#[arg(help = "Addon name (e.g. cert-manager, ingress, dashboard)")]
name: String,
},
Disable {
#[arg(help = "Addon name (e.g. cert-manager, ingress, dashboard)")]
name: String,
},
}
#[cfg(feature = "kubernetes")]
#[derive(Subcommand, Debug)]
pub enum KubernetesSubCommand {
Check {
#[arg(long, help = "Kubeconfig context to target")]
context: Option<String>,
#[arg(
long,
default_value = "default",
help = "Namespace to probe for workload readiness"
)]
namespace: String,
#[arg(long, help = "Skip live cluster calls (tooling check only)")]
offline: bool,
},
Generate {
#[arg(long, help = "Logical app name (used for resource names)")]
name: String,
#[arg(long, help = "Container image reference")]
image: String,
#[arg(long, default_value_t = 80, help = "Container port for the service")]
port: u16,
#[arg(long, default_value_t = 1, help = "Replica count")]
replicas: u16,
#[arg(
long,
default_value = "default",
help = "Namespace for generated resources"
)]
namespace: String,
#[arg(
long,
default_value = "k8s/xbp-manifest.yaml",
help = "Path to write the manifest bundle"
)]
output: String,
#[arg(long, help = "Optional ingress host (creates Ingress when set)")]
host: Option<String>,
},
Apply {
#[arg(long, help = "Path to manifest file")]
file: String,
#[arg(long, help = "Override kube context")]
context: Option<String>,
#[arg(long, help = "Override namespace")]
namespace: Option<String>,
#[arg(long, help = "Use --dry-run=server")]
dry_run: bool,
},
Status {
#[arg(long, default_value = "default", help = "Namespace to summarize")]
namespace: String,
#[arg(long, help = "Override kube context")]
context: Option<String>,
},
Addons(KubernetesAddonCmd),
DashboardToken {
#[arg(
long,
default_value = "kube-system",
help = "Namespace containing the dashboard token secret"
)]
namespace: String,
#[arg(
long,
default_value = "microk8s-dashboard-token",
help = "Secret name containing the dashboard login token"
)]
secret: String,
#[arg(long, help = "Override kube context")]
context: Option<String>,
},
ObservabilityCreds {
#[arg(
long,
default_value = "observability",
help = "Namespace containing Grafana secret"
)]
namespace: String,
#[arg(
long,
default_value = "kube-prom-stack-grafana",
help = "Grafana secret name"
)]
secret: String,
#[arg(long, help = "Override kube context")]
context: Option<String>,
},
Issuer {
#[arg(
long,
help = "Email used for Let's Encrypt account registration (required)"
)]
email: String,
#[arg(long, default_value = "letsencrypt", help = "Issuer resource name")]
name: String,
#[arg(
long,
default_value = "default",
help = "Namespace for the Issuer resource"
)]
namespace: String,
#[arg(
long,
default_value = "https://acme-v02.api.letsencrypt.org/directory",
help = "ACME server URL (production by default)"
)]
server: String,
#[arg(
long,
default_value = "letsencrypt-account-key",
help = "Secret used to store the ACME account private key"
)]
private_key_secret: String,
#[arg(
long,
default_value = "nginx",
help = "Ingress class name used for HTTP01 solving"
)]
ingress_class_name: String,
#[arg(long, help = "Override kube context")]
context: Option<String>,
#[arg(long, help = "Use --dry-run=server")]
dry_run: bool,
},
}
#[cfg(test)]
mod tests {
use super::{Cli, Commands, NetworkFloatingIpSubCommand, NetworkSubCommand};
use clap::Parser;
#[test]
fn parses_network_floating_ip_add() {
let cli = Cli::parse_from([
"xbp",
"network",
"floating-ip",
"add",
"--ip",
"1.2.3.4",
"--apply",
]);
match cli.command {
Some(Commands::Network(network)) => match network.command {
NetworkSubCommand::FloatingIp(fip) => match fip.command {
NetworkFloatingIpSubCommand::Add { ip, apply, .. } => {
assert_eq!(ip, "1.2.3.4");
assert!(apply);
}
_ => panic!("expected add subcommand"),
},
_ => panic!("expected floating-ip subcommand"),
},
_ => panic!("expected network command"),
}
}
}