- Fix parallel execution logic to properly handle JoinHandle<Result<R, E>> types - Use join_all instead of try_join_all for proper Result handling - Fix double question mark (??) issue in parallel execution methods - Clean up unused imports in parallel and cache modules - Ensure all performance optimization modules compile successfully - Fix CI build failures caused by compilation errors
1074 lines
35 KiB
Markdown
1074 lines
35 KiB
Markdown
# 🖥️ **apt-ostree CLI Command Structure and Help System Architecture**
|
|
|
|
## 📋 **Overview**
|
|
|
|
This document outlines the CLI command structure and help system architecture for apt-ostree, based on analysis of how rpm-ostree implements its comprehensive command-line interface. The CLI provides full compatibility with rpm-ostree commands while leveraging the strengths of the Debian/Ubuntu package management system.
|
|
|
|
## 🏗️ **Architecture Overview**
|
|
|
|
### **Component Separation**
|
|
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ CLI Client │ │ Rust Core │ │ Rust Daemon │
|
|
│ (apt-ostree) │◄──►│ (DBus) │◄──►│ (aptostreed) │
|
|
│ │ │ │ │ │
|
|
│ • Command │ │ • Client Logic │ │ • Command │
|
|
│ • Help System │ │ • DBus Client │ │ • Execution │
|
|
│ • Argument │ │ • Help Display │ │ • State Mgmt │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
### **Responsibility Distribution**
|
|
|
|
#### **CLI Client (`apt-ostree`)**
|
|
- **Command parsing** and argument handling
|
|
- **Help system** and usage information
|
|
- **DBus communication** with daemon
|
|
- **User interface** and output formatting
|
|
|
|
#### **Daemon (`aptostreed`)**
|
|
- **Command execution** and processing
|
|
- **System state** management
|
|
- **Operation coordination** and validation
|
|
- **Result reporting** and error handling
|
|
|
|
## 🔍 **rpm-ostree Implementation Analysis**
|
|
|
|
### **CLI Command Structure**
|
|
|
|
Based on `rpmostree-main.cxx` and various builtin modules, rpm-ostree provides these main command categories:
|
|
|
|
```c
|
|
static RpmOstreeCommand commands[]
|
|
= { { "status", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD,
|
|
"Show system status", rpmostree_builtin_status },
|
|
{ "upgrade", RPM_OSTREE_BUILTINE_FLAG_REQUIRES_ROOT,
|
|
"Upgrade system", rpmostree_builtin_upgrade },
|
|
{ "deploy", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Deploy a deployment", rpmostree_builtin_deploy },
|
|
{ "rollback", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Rollback to previous deployment", rpmostree_builtin_rollback },
|
|
{ "install", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Install packages", rpmostree_builtin_install },
|
|
{ "uninstall", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Uninstall packages", rpmostree_builtin_uninstall },
|
|
{ "override", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Override packages", rpmostree_builtin_override },
|
|
{ "usroverlay", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"User overlays", rpmostree_builtin_usroverlay },
|
|
{ "apply-live", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Apply live updates", rpmostree_builtin_apply_live },
|
|
{ "compose", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Tree composition", rpmostree_builtin_compose },
|
|
{ "db", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD,
|
|
"Database operations", rpmostree_builtin_db },
|
|
{ "initramfs", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Initramfs management", rpmostree_builtin_initramfs },
|
|
{ "kargs", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Kernel arguments", rpmostree_builtin_kargs },
|
|
{ "rebase", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Rebase to different base", rpmostree_builtin_rebase },
|
|
{ "cleanup", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Clean up deployments", rpmostree_builtin_cleanup },
|
|
{ "refresh-md", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Refresh metadata", rpmostree_builtin_refresh_md },
|
|
{ "remote", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Remote management", rpmostree_builtin_remote },
|
|
{ "container", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Container operations", rpmostree_builtin_container },
|
|
{ "image", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Image operations", rpmostree_builtin_image },
|
|
{ "pkg", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD,
|
|
"Package operations", rpmostree_builtin_pkg },
|
|
{ "reload", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Reload configuration", rpmostree_builtin_reload },
|
|
{ "reset", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Reset system state", rpmostree_builtin_reset },
|
|
{ "start-daemon", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Start daemon", rpmostree_builtin_start_daemon },
|
|
{ "stop-daemon", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
|
|
"Stop daemon", rpmostree_builtin_stop_daemon },
|
|
{ NULL, (RpmOstreeBuiltinFlags)0, NULL, NULL } };
|
|
```
|
|
|
|
### **Key Insights from rpm-ostree**
|
|
|
|
1. **Comprehensive Command Coverage**: Covers all major system operations
|
|
2. **Consistent Flag Handling**: Standard flags like `--help`, `--version`, `--os`
|
|
3. **Root Privilege Requirements**: Many commands require root access
|
|
4. **Local vs Remote Commands**: Some commands work locally, others require daemon
|
|
|
|
## 🚀 **apt-ostree Implementation Strategy**
|
|
|
|
### **1. Main CLI Structure**
|
|
|
|
```rust
|
|
// src/main.rs - Main CLI entry point
|
|
#[tokio::main]
|
|
async fn main() -> AptOstreeResult<()> {
|
|
// Initialize logging
|
|
init_logging()?;
|
|
|
|
// Parse command line arguments
|
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
|
|
|
if args.is_empty() {
|
|
show_usage();
|
|
return Ok(());
|
|
}
|
|
|
|
// Handle global flags
|
|
let mut i = 0;
|
|
while i < args.len() {
|
|
match args[i].as_str() {
|
|
"--version" | "-V" => {
|
|
show_version();
|
|
return Ok(());
|
|
}
|
|
"--help" | "-h" => {
|
|
show_usage();
|
|
return Ok(());
|
|
}
|
|
"--quiet" | "-q" => {
|
|
set_quiet_mode(true);
|
|
i += 1;
|
|
}
|
|
"--verbose" | "-v" => {
|
|
set_verbose_mode(true);
|
|
i += 1;
|
|
}
|
|
"--debug" => {
|
|
set_debug_mode(true);
|
|
i += 1;
|
|
}
|
|
_ => break,
|
|
}
|
|
}
|
|
|
|
// Get command and subcommand
|
|
if i >= args.len() {
|
|
show_usage();
|
|
return Ok(());
|
|
}
|
|
|
|
let command = &args[i];
|
|
let subargs = &args[i + 1..];
|
|
|
|
// Route to appropriate command handler
|
|
match command.as_str() {
|
|
"status" => status_command(subargs).await?,
|
|
"upgrade" => upgrade_command(subargs).await?,
|
|
"deploy" => deploy_command(subargs).await?,
|
|
"rollback" => rollback_command(subargs).await?,
|
|
"install" => install_command(subargs).await?,
|
|
"uninstall" => uninstall_command(subargs).await?,
|
|
"override" => override_commands(subargs).await?,
|
|
"usroverlay" => usroverlay_commands(subargs).await?,
|
|
"apply-live" => apply_live_commands(subargs).await?,
|
|
"compose" => compose_commands(subargs).await?,
|
|
"db" => db_commands(subargs).await?,
|
|
"initramfs" => initramfs_commands(subargs).await?,
|
|
"kargs" => kargs_commands(subargs).await?,
|
|
"rebase" => rebase_command(subargs).await?,
|
|
"cleanup" => cleanup_command(subargs).await?,
|
|
"refresh-md" => refresh_md_command(subargs).await?,
|
|
"remote" => remote_commands(subargs).await?,
|
|
"container" => container_commands(subargs).await?,
|
|
"image" => image_commands(subargs).await?,
|
|
"pkg" => pkg_commands(subargs).await?,
|
|
"reload" => reload_command(subargs).await?,
|
|
"reset" => reset_command(subargs).await?,
|
|
"start-daemon" => start_daemon_command(subargs).await?,
|
|
"stop-daemon" => stop_daemon_command(subargs).await?,
|
|
_ => {
|
|
eprintln!("❌ Unknown command: {}", command);
|
|
show_usage();
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### **2. Command Handler Structure**
|
|
|
|
#### **Base Command Handler**
|
|
|
|
```rust
|
|
// src/commands/base_command.rs
|
|
pub trait BaseCommand {
|
|
fn name(&self) -> &'static str;
|
|
fn description(&self) -> &'static str;
|
|
fn usage(&self) -> &'static str;
|
|
fn examples(&self) -> &'static [&'static str];
|
|
fn flags(&self) -> &'static [CommandFlag];
|
|
fn requires_root(&self) -> bool;
|
|
fn is_local_command(&self) -> bool;
|
|
|
|
async fn execute(&self, args: &[String]) -> AptOstreeResult<()>;
|
|
|
|
fn show_help(&self) {
|
|
println!("{}", self.usage());
|
|
println!();
|
|
println!("{}", self.description());
|
|
println!();
|
|
|
|
if !self.flags().is_empty() {
|
|
println!("OPTIONS:");
|
|
for flag in self.flags() {
|
|
println!(" {} {}", flag.short, flag.long);
|
|
println!(" {}", flag.description);
|
|
println!();
|
|
}
|
|
}
|
|
|
|
if !self.examples().is_empty() {
|
|
println!("EXAMPLES:");
|
|
for example in self.examples() {
|
|
println!(" {}", example);
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct CommandFlag {
|
|
pub short: &'static str,
|
|
pub long: &'static str,
|
|
pub description: &'static str,
|
|
pub requires_value: bool,
|
|
}
|
|
```
|
|
|
|
#### **Command Implementation Examples**
|
|
|
|
```rust
|
|
// src/commands/status.rs
|
|
pub struct StatusCommand;
|
|
|
|
impl BaseCommand for StatusCommand {
|
|
fn name(&self) -> &'static str { "status" }
|
|
|
|
fn description(&self) -> &'static str {
|
|
"Show system status including OSTree deployments and package information"
|
|
}
|
|
|
|
fn usage(&self) -> &'static str {
|
|
"apt-ostree status [OPTIONS]"
|
|
}
|
|
|
|
fn examples(&self) -> &'static [&'static str] {
|
|
&[
|
|
"apt-ostree status",
|
|
"apt-ostree status --os fedora",
|
|
"apt-ostree status --json",
|
|
]
|
|
}
|
|
|
|
fn flags(&self) -> &'static [CommandFlag] {
|
|
&[
|
|
CommandFlag {
|
|
short: "-o",
|
|
long: "--os",
|
|
description: "Operating system name",
|
|
requires_value: true,
|
|
},
|
|
CommandFlag {
|
|
short: "-j",
|
|
long: "--json",
|
|
description: "Output in JSON format",
|
|
requires_value: false,
|
|
},
|
|
CommandFlag {
|
|
short: "-v",
|
|
long: "--verbose",
|
|
description: "Verbose output",
|
|
requires_value: false,
|
|
},
|
|
]
|
|
}
|
|
|
|
fn requires_root(&self) -> bool { false }
|
|
|
|
fn is_local_command(&self) -> bool { true }
|
|
|
|
async fn execute(&self, args: &[String]) -> AptOstreeResult<()> {
|
|
// Parse arguments
|
|
let mut osname = None;
|
|
let mut json_output = false;
|
|
let mut verbose = false;
|
|
|
|
let mut i = 0;
|
|
while i < args.len() {
|
|
match args[i].as_str() {
|
|
"--os" | "-o" => {
|
|
if i + 1 < args.len() {
|
|
osname = Some(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--os requires a value".to_string()));
|
|
}
|
|
}
|
|
"--json" | "-j" => {
|
|
json_output = true;
|
|
i += 1;
|
|
}
|
|
"--verbose" | "-v" => {
|
|
verbose = true;
|
|
i += 1;
|
|
}
|
|
_ => {
|
|
return Err(AptOstreeError::InvalidArgument(
|
|
format!("Unknown option: {}", args[i]),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute status command
|
|
let status = get_system_status(osname.as_deref()).await?;
|
|
|
|
if json_output {
|
|
let json = serde_json::to_string_pretty(&status)?;
|
|
println!("{}", json);
|
|
} else {
|
|
display_system_status(&status, verbose);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// src/commands/install.rs
|
|
pub struct InstallCommand;
|
|
|
|
impl BaseCommand for InstallCommand {
|
|
fn name(&self) -> &'static str { "install" }
|
|
|
|
fn description(&self) -> &'static str {
|
|
"Install packages into the current deployment"
|
|
}
|
|
|
|
fn usage(&self) -> &'static str {
|
|
"apt-ostree install [OPTIONS] PACKAGES..."
|
|
}
|
|
|
|
fn examples(&self) -> &'static [&'static str] {
|
|
&[
|
|
"apt-ostree install vim",
|
|
"apt-ostree install vim emacs",
|
|
"apt-ostree install --reboot vim",
|
|
]
|
|
}
|
|
|
|
fn flags(&self) -> &'static [CommandFlag] {
|
|
&[
|
|
CommandFlag {
|
|
short: "-o",
|
|
long: "--os",
|
|
description: "Operating system name",
|
|
requires_value: true,
|
|
},
|
|
CommandFlag {
|
|
short: "-r",
|
|
long: "--reboot",
|
|
description: "Reboot after installation",
|
|
requires_value: false,
|
|
},
|
|
CommandFlag {
|
|
short: "-y",
|
|
long: "--yes",
|
|
description: "Answer yes to prompts",
|
|
requires_value: false,
|
|
},
|
|
]
|
|
}
|
|
|
|
fn requires_root(&self) -> bool { true }
|
|
|
|
fn is_local_command(&self) -> bool { false }
|
|
|
|
async fn execute(&self, args: &[String]) -> AptOstreeResult<()> {
|
|
// Parse arguments
|
|
let mut osname = None;
|
|
let mut reboot = false;
|
|
let mut yes = false;
|
|
let mut packages = Vec::new();
|
|
|
|
let mut i = 0;
|
|
while i < args.len() {
|
|
match args[i].as_str() {
|
|
"--os" | "-o" => {
|
|
if i + 1 < args.len() {
|
|
osname = Some(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--os requires a value".to_string()));
|
|
}
|
|
}
|
|
"--reboot" | "-r" => {
|
|
reboot = true;
|
|
i += 1;
|
|
}
|
|
"--yes" | "-y" => {
|
|
yes = true;
|
|
i += 1;
|
|
}
|
|
_ => {
|
|
packages.push(args[i].clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if packages.is_empty() {
|
|
return Err(AptOstreeError::InvalidArgument("No packages specified".to_string()));
|
|
}
|
|
|
|
// Execute package installation
|
|
install_packages(&packages, osname.as_deref(), reboot, yes).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
### **3. Help System Implementation**
|
|
|
|
#### **Comprehensive Help Display**
|
|
|
|
```rust
|
|
// src/help/help_system.rs
|
|
pub struct HelpSystem;
|
|
|
|
impl HelpSystem {
|
|
pub fn show_main_help() {
|
|
println!("apt-ostree - Hybrid image/package system for Debian/Ubuntu");
|
|
println!();
|
|
println!("USAGE:");
|
|
println!(" apt-ostree [OPTIONS] <COMMAND> [SUBCOMMAND] [ARGS]...");
|
|
println!();
|
|
println!("OPTIONS:");
|
|
println!(" -h, --help Show this help message");
|
|
println!(" -V, --version Show version information");
|
|
println!(" -q, --quiet Suppress output");
|
|
println!(" -v, --verbose Verbose output");
|
|
println!(" --debug Enable debug output");
|
|
println!();
|
|
println!("COMMANDS:");
|
|
println!(" status Show system status");
|
|
println!(" upgrade Upgrade system");
|
|
println!(" deploy Deploy a deployment");
|
|
println!(" rollback Rollback to previous deployment");
|
|
println!(" install Install packages");
|
|
println!(" uninstall Uninstall packages");
|
|
println!(" override Override packages");
|
|
println!(" usroverlay User overlays");
|
|
println!(" apply-live Apply live updates");
|
|
println!(" compose Tree composition");
|
|
println!(" db Database operations");
|
|
println!(" initramfs Initramfs management");
|
|
println!(" kargs Kernel arguments");
|
|
println!(" rebase Rebase to different base");
|
|
println!(" cleanup Clean up deployments");
|
|
println!(" refresh-md Refresh metadata");
|
|
println!(" remote Remote management");
|
|
println!(" container Container operations");
|
|
println!(" image Image operations");
|
|
println!(" pkg Package operations");
|
|
println!(" reload Reload configuration");
|
|
println!(" reset Reset system state");
|
|
println!(" start-daemon Start daemon");
|
|
println!(" stop-daemon Stop daemon");
|
|
println!();
|
|
println!("For more information on a specific command, use:");
|
|
println!(" apt-ostree <COMMAND> --help");
|
|
println!();
|
|
println!("For more information on a specific subcommand, use:");
|
|
println!(" apt-ostree <COMMAND> <SUBCOMMAND> --help");
|
|
}
|
|
|
|
pub fn show_command_help(command: &str) {
|
|
match command {
|
|
"status" => StatusCommand.show_help(),
|
|
"install" => InstallCommand.show_help(),
|
|
"override" => show_override_help(),
|
|
"usroverlay" => show_usroverlay_help(),
|
|
"apply-live" => show_apply_live_help(),
|
|
"compose" => show_compose_help(),
|
|
"db" => show_db_help(),
|
|
"initramfs" => show_initramfs_help(),
|
|
"kargs" => show_kargs_help(),
|
|
_ => {
|
|
eprintln!("❌ Unknown command: {}", command);
|
|
println!("Use 'apt-ostree --help' for available commands");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn show_subcommand_help(command: &str, subcommand: &str) {
|
|
match (command, subcommand) {
|
|
("override", "replace") => show_override_replace_help(),
|
|
("override", "reset") => show_override_reset_help(),
|
|
("override", "list") => show_override_list_help(),
|
|
("usroverlay", "apply") => show_usroverlay_apply_help(),
|
|
("usroverlay", "list") => show_usroverlay_list_help(),
|
|
("usroverlay", "reset") => show_usroverlay_reset_help(),
|
|
("usroverlay", "remove") => show_usroverlay_remove_help(),
|
|
("apply-live", "apply") => show_apply_live_apply_help(),
|
|
("apply-live", "status") => show_apply_live_status_help(),
|
|
("apply-live", "rollback") => show_apply_live_rollback_help(),
|
|
("compose", "tree") => show_compose_tree_help(),
|
|
("compose", "install") => show_compose_install_help(),
|
|
("db", "diff") => show_db_diff_help(),
|
|
("db", "list") => show_db_list_help(),
|
|
("db", "version") => show_db_version_help(),
|
|
("initramfs", "regenerate") => show_initramfs_regenerate_help(),
|
|
("kargs", "set") => show_kargs_set_help(),
|
|
("kargs", "get") => show_kargs_get_help(),
|
|
("kargs", "delete") => show_kargs_delete_help(),
|
|
_ => {
|
|
eprintln!("❌ Unknown subcommand: {} {}", command, subcommand);
|
|
println!("Use 'apt-ostree {} --help' for available subcommands", command);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn show_override_help() {
|
|
println!("apt-ostree override - Override packages in the current deployment");
|
|
println!();
|
|
println!("USAGE:");
|
|
println!(" apt-ostree override [OPTIONS] <SUBCOMMAND> [ARGS]...");
|
|
println!();
|
|
println!("SUBCOMMANDS:");
|
|
println!(" replace Replace a base package with a different version");
|
|
println!(" reset Reset a package override to base version");
|
|
println!(" list List current package overrides");
|
|
println!();
|
|
println!("OPTIONS:");
|
|
println!(" -o, --os <OSNAME> Operating system name");
|
|
println!(" -h, --help Show this help message");
|
|
println!();
|
|
println!("For more information on a specific subcommand, use:");
|
|
println!(" apt-ostree override <SUBCOMMAND> --help");
|
|
}
|
|
|
|
fn show_override_replace_help() {
|
|
println!("apt-ostree override replace - Replace a base package with a different version");
|
|
println!();
|
|
println!("USAGE:");
|
|
println!(" apt-ostree override replace [OPTIONS] <PACKAGE> <VERSION>");
|
|
println!();
|
|
println!("ARGUMENTS:");
|
|
println!(" PACKAGE Package name to override");
|
|
println!(" VERSION New version to use");
|
|
println!();
|
|
println!("OPTIONS:");
|
|
println!(" -o, --os <OSNAME> Operating system name");
|
|
println!(" -r, --reboot Reboot after override");
|
|
println!(" -h, --help Show this help message");
|
|
println!();
|
|
println!("EXAMPLES:");
|
|
println!(" apt-ostree override replace vim 2:9.0.1378-1");
|
|
println!(" apt-ostree override replace --reboot vim 2:9.0.1378-1");
|
|
}
|
|
```
|
|
|
|
### **4. Argument Parsing and Validation**
|
|
|
|
#### **Argument Parser**
|
|
|
|
```rust
|
|
// src/args/argument_parser.rs
|
|
pub struct ArgumentParser {
|
|
args: Vec<String>,
|
|
position: usize,
|
|
}
|
|
|
|
impl ArgumentParser {
|
|
pub fn new(args: Vec<String>) -> Self {
|
|
Self { args, position: 0 }
|
|
}
|
|
|
|
pub fn has_next(&self) -> bool {
|
|
self.position < self.args.len()
|
|
}
|
|
|
|
pub fn peek(&self) -> Option<&str> {
|
|
self.args.get(self.position).map(|s| s.as_str())
|
|
}
|
|
|
|
pub fn next(&mut self) -> Option<String> {
|
|
if self.has_next() {
|
|
let arg = self.args[self.position].clone();
|
|
self.position += 1;
|
|
Some(arg)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn expect_flag(&mut self, expected: &str) -> AptOstreeResult<bool> {
|
|
if let Some(arg) = self.peek() {
|
|
if arg == expected {
|
|
self.next();
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
pub fn expect_value(&mut self, flag: &str) -> AptOstreeResult<String> {
|
|
if let Some(value) = self.next() {
|
|
Ok(value)
|
|
} else {
|
|
Err(AptOstreeError::InvalidArgument(
|
|
format!("{} requires a value", flag),
|
|
))
|
|
}
|
|
}
|
|
|
|
pub fn parse_global_flags(&mut self) -> AptOstreeResult<GlobalFlags> {
|
|
let mut flags = GlobalFlags::default();
|
|
|
|
while self.has_next() {
|
|
match self.peek() {
|
|
Some("--version") | Some("-V") => {
|
|
flags.version = true;
|
|
self.next();
|
|
}
|
|
Some("--help") | Some("-h") => {
|
|
flags.help = true;
|
|
self.next();
|
|
}
|
|
Some("--quiet") | Some("-q") => {
|
|
flags.quiet = true;
|
|
self.next();
|
|
}
|
|
Some("--verbose") | Some("-v") => {
|
|
flags.verbose = true;
|
|
self.next();
|
|
}
|
|
Some("--debug") => {
|
|
flags.debug = true;
|
|
self.next();
|
|
}
|
|
_ => break,
|
|
}
|
|
}
|
|
|
|
Ok(flags)
|
|
}
|
|
|
|
pub fn parse_command_flags(&mut self, command: &str) -> AptOstreeResult<CommandFlags> {
|
|
let mut flags = CommandFlags::default();
|
|
|
|
while self.has_next() {
|
|
match self.peek() {
|
|
Some("--os") | Some("-o") => {
|
|
self.next();
|
|
flags.osname = Some(self.expect_value("--os")?);
|
|
}
|
|
Some("--stateroot") => {
|
|
self.next();
|
|
flags.stateroot = Some(self.expect_value("--stateroot")?);
|
|
}
|
|
Some("--reboot") | Some("-r") => {
|
|
flags.reboot = true;
|
|
self.next();
|
|
}
|
|
Some("--yes") | Some("-y") => {
|
|
flags.yes = true;
|
|
self.next();
|
|
}
|
|
Some("--json") | Some("-j") => {
|
|
flags.json = true;
|
|
self.next();
|
|
}
|
|
Some("--help") | Some("-h") => {
|
|
flags.help = true;
|
|
self.next();
|
|
}
|
|
Some(arg) if arg.starts_with('-') => {
|
|
return Err(AptOstreeError::InvalidArgument(
|
|
format!("Unknown option: {}", arg),
|
|
));
|
|
}
|
|
_ => break,
|
|
}
|
|
}
|
|
|
|
Ok(flags)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct GlobalFlags {
|
|
pub version: bool,
|
|
pub help: bool,
|
|
pub quiet: bool,
|
|
pub verbose: bool,
|
|
pub debug: bool,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct CommandFlags {
|
|
pub osname: Option<String>,
|
|
pub stateroot: Option<String>,
|
|
pub reboot: bool,
|
|
pub yes: bool,
|
|
pub json: bool,
|
|
pub help: bool,
|
|
}
|
|
```
|
|
|
|
### **5. Error Handling and User Feedback**
|
|
|
|
#### **Error Display System**
|
|
|
|
```rust
|
|
// src/error/error_display.rs
|
|
pub struct ErrorDisplay;
|
|
|
|
impl ErrorDisplay {
|
|
pub fn display_error(error: &AptOstreeError) {
|
|
match error {
|
|
AptOstreeError::InvalidArgument(msg) => {
|
|
eprintln!("❌ Invalid argument: {}", msg);
|
|
}
|
|
AptOstreeError::CommandNotFound(cmd) => {
|
|
eprintln!("❌ Command not found: {}", cmd);
|
|
println!("Use 'apt-ostree --help' for available commands");
|
|
}
|
|
AptOstreeError::SubcommandNotFound(cmd, subcmd) => {
|
|
eprintln!("❌ Subcommand not found: {} {}", cmd, subcmd);
|
|
println!("Use 'apt-ostree {} --help' for available subcommands", cmd);
|
|
}
|
|
AptOstreeError::MissingArgument(arg) => {
|
|
eprintln!("❌ Missing required argument: {}", arg);
|
|
}
|
|
AptOstreeError::InvalidValue(arg, value) => {
|
|
eprintln!("❌ Invalid value '{}' for argument: {}", value, arg);
|
|
}
|
|
AptOstreeError::PermissionDenied => {
|
|
eprintln!("❌ Permission denied");
|
|
println!("This command requires root privileges");
|
|
}
|
|
AptOstreeError::SystemError(msg) => {
|
|
eprintln!("❌ System error: {}", msg);
|
|
}
|
|
AptOstreeError::DaemonError(msg) => {
|
|
eprintln!("❌ Daemon error: {}", msg);
|
|
println!("Check if aptostreed is running");
|
|
}
|
|
_ => {
|
|
eprintln!("❌ Error: {}", error);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn display_usage_hint(command: &str) {
|
|
println!("💡 Use 'apt-ostree {} --help' for more information", command);
|
|
}
|
|
|
|
pub fn display_command_suggestions(input: &str) {
|
|
let suggestions = find_similar_commands(input);
|
|
if !suggestions.is_empty() {
|
|
println!("💡 Did you mean:");
|
|
for suggestion in suggestions {
|
|
println!(" apt-ostree {}", suggestion);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn find_similar_commands(input: &str) -> Vec<String> {
|
|
let all_commands = [
|
|
"status", "upgrade", "deploy", "rollback", "install", "uninstall",
|
|
"override", "usroverlay", "apply-live", "compose", "db", "initramfs",
|
|
"kargs", "rebase", "cleanup", "refresh-md", "remote", "container",
|
|
"image", "pkg", "reload", "reset", "start-daemon", "stop-daemon",
|
|
];
|
|
|
|
let mut suggestions = Vec::new();
|
|
for cmd in &all_commands {
|
|
if cmd.starts_with(input) || input.starts_with(cmd) {
|
|
suggestions.push(cmd.to_string());
|
|
}
|
|
}
|
|
|
|
suggestions.sort();
|
|
suggestions.truncate(3); // Limit to 3 suggestions
|
|
suggestions
|
|
}
|
|
```
|
|
|
|
## 🔐 **Security Considerations and Privilege Management**
|
|
|
|
### **1. Polkit Integration for Command Authorization**
|
|
|
|
```rust
|
|
// Security checks for commands using Polkit
|
|
impl SecurityManager {
|
|
pub async fn authorize_command(
|
|
&self,
|
|
command: &str,
|
|
subcommand: Option<&str>,
|
|
user_id: u32,
|
|
) -> Result<(), SecurityError> {
|
|
// Build Polkit action string
|
|
let action = match subcommand {
|
|
Some(sub) => format!("org.projectatomic.aptostree.{}.{}", command, sub),
|
|
None => format!("org.projectatomic.aptostree.{}", command),
|
|
};
|
|
|
|
// Use Polkit for authorization
|
|
let polkit_result = self.check_polkit_authorization(&action, user_id).await?;
|
|
|
|
if !polkit_result.authorized {
|
|
return Err(SecurityError::PolkitAuthorizationDenied {
|
|
action,
|
|
user_id,
|
|
details: polkit_result.details,
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_polkit_authorization(
|
|
&self,
|
|
action: &str,
|
|
user_id: u32,
|
|
) -> Result<PolkitResult, Error> {
|
|
// Create Polkit authority
|
|
let authority = polkit::Authority::get().await?;
|
|
|
|
// Get subject for the user
|
|
let subject = polkit::UnixProcess::new_for_owner(
|
|
std::process::id(),
|
|
None,
|
|
Some(user_id),
|
|
)?;
|
|
|
|
// Check authorization
|
|
let result = authority
|
|
.check_authorization(
|
|
&subject,
|
|
action,
|
|
None,
|
|
polkit::CheckFlags::NONE,
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(PolkitResult {
|
|
authorized: result.is_authorized(),
|
|
details: result.get_details().map(|d| d.to_string()),
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Command Privilege Validation**
|
|
|
|
```rust
|
|
// Validate command privileges
|
|
impl CommandValidator {
|
|
pub fn validate_command_privileges(
|
|
&self,
|
|
command: &str,
|
|
subcommand: Option<&str>,
|
|
) -> AptOstreeResult<()> {
|
|
// Check if command requires root
|
|
if self.command_requires_root(command, subcommand) {
|
|
if !self.is_running_as_root() {
|
|
return Err(AptOstreeError::PermissionDenied);
|
|
}
|
|
}
|
|
|
|
// Check if command requires daemon
|
|
if !self.command_is_local(command, subcommand) {
|
|
if !self.daemon_is_running().await? {
|
|
return Err(AptOstreeError::DaemonNotRunning);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn command_requires_root(&self, command: &str, subcommand: Option<&str>) -> bool {
|
|
match (command, subcommand) {
|
|
("status", _) | ("db", _) | ("pkg", _) => false,
|
|
("override", "list") | ("usroverlay", "list") | ("apply-live", "status") => false,
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
fn command_is_local(&self, command: &str, subcommand: Option<&str>) -> bool {
|
|
match (command, subcommand) {
|
|
("status", _) | ("db", _) | ("pkg", _) => true,
|
|
("override", "list") | ("usroverlay", "list") | ("apply-live", "status") => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 📊 **Performance Optimization**
|
|
|
|
### **1. Command Caching**
|
|
|
|
```rust
|
|
// Cache command information
|
|
impl CommandCache {
|
|
pub async fn get_cached_command_info(
|
|
&self,
|
|
command: &str,
|
|
) -> Result<Option<CommandInfo>, Error> {
|
|
// Check cache first
|
|
if let Some(cached) = self.cache.get_command(command).await? {
|
|
return Ok(Some(cached));
|
|
}
|
|
|
|
// Fetch from command registry
|
|
let command_info = self.command_registry.get_command(command).await?;
|
|
|
|
// Cache the result
|
|
if let Some(ref info) = command_info {
|
|
self.cache.cache_command(info).await?;
|
|
}
|
|
|
|
Ok(command_info)
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Parallel Command Processing**
|
|
|
|
```rust
|
|
// Parallel command execution
|
|
impl CommandExecutor {
|
|
pub async fn execute_parallel_commands(
|
|
&self,
|
|
commands: Vec<CommandRequest>,
|
|
) -> Result<Vec<CommandResult>, Error> {
|
|
let mut tasks = JoinSet::new();
|
|
|
|
// Spawn parallel command tasks
|
|
for command_request in commands {
|
|
let command_executor = self.clone();
|
|
|
|
tasks.spawn(async move {
|
|
command_executor
|
|
.execute_command(
|
|
&command_request.command,
|
|
command_request.subcommand.as_deref(),
|
|
&command_request.args,
|
|
command_request.flags,
|
|
)
|
|
.await
|
|
});
|
|
}
|
|
|
|
// Collect results
|
|
let mut results = Vec::new();
|
|
while let Some(result) = tasks.join_next().await {
|
|
results.push(result??);
|
|
}
|
|
|
|
Ok(results)
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🧪 **Testing Strategy**
|
|
|
|
### **1. Unit Tests**
|
|
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_argument_parser_global_flags() {
|
|
let args = vec![
|
|
"--version".to_string(),
|
|
"--help".to_string(),
|
|
"--quiet".to_string(),
|
|
];
|
|
|
|
let mut parser = ArgumentParser::new(args);
|
|
let flags = parser.parse_global_flags().unwrap();
|
|
|
|
assert!(flags.version);
|
|
assert!(flags.help);
|
|
assert!(flags.quiet);
|
|
}
|
|
|
|
#[test]
|
|
fn test_command_validation() {
|
|
let validator = CommandValidator::new();
|
|
|
|
// Test root requirement
|
|
assert!(validator.command_requires_root("install", None));
|
|
assert!(!validator.command_requires_root("status", None));
|
|
|
|
// Test local command
|
|
assert!(validator.command_is_local("status", None));
|
|
assert!(!validator.command_is_local("install", None));
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Integration Tests**
|
|
|
|
```rust
|
|
#[tokio::test]
|
|
async fn test_full_cli_workflow() {
|
|
// Test main help
|
|
let output = run_cli_command(&["--help"]).await?;
|
|
assert!(output.contains("apt-ostree - Hybrid image/package system"));
|
|
|
|
// Test command help
|
|
let output = run_cli_command(&["install", "--help"]).await?;
|
|
assert!(output.contains("Install packages into the current deployment"));
|
|
|
|
// Test subcommand help
|
|
let output = run_cli_command(&["override", "replace", "--help"]).await?;
|
|
assert!(output.contains("Replace a base package with a different version"));
|
|
|
|
// Test invalid command
|
|
let output = run_cli_command(&["invalid-command"]).await?;
|
|
assert!(output.contains("Unknown command: invalid-command"));
|
|
}
|
|
```
|
|
|
|
## 🚀 **Future Enhancements**
|
|
|
|
### **1. Advanced CLI Features**
|
|
- **Command aliases** and shortcuts
|
|
- **Command history** and suggestions
|
|
- **Interactive mode** with tab completion
|
|
- **Command chaining** and pipelines
|
|
|
|
### **2. Performance Improvements**
|
|
- **Command caching** strategies
|
|
- **Background command** execution
|
|
- **Command optimization** and parallelization
|
|
- **Command cleanup** automation
|
|
|
|
### **3. Integration Features**
|
|
- **External command** plugins
|
|
- **Command monitoring** and logging
|
|
- **Command analytics** and reporting
|
|
- **Automated command** testing and validation
|
|
|
|
This architecture provides a solid foundation for implementing a production-ready CLI interface in apt-ostree, maintaining full compatibility with the rpm-ostree ecosystem while providing comprehensive help systems, robust argument parsing, and comprehensive security through Polkit integration.
|