Skip to content

Commit

Permalink
fix: create CLI skeleton and engine blueprint
Browse files Browse the repository at this point in the history
You know what they say: Rome wasn't built in a day. They actually
started by creating an empty skeleton the city and a blueprint of its
government first.

1. We have a semi-complete CLI implemetantion
2. We have integration podman-api crate to make calls to podman
3. We don't make calls yet

We will probably get distracted working on CI after this, c'est la vie.
  • Loading branch information
akdev1l committed Jan 29, 2023
1 parent 7afa37d commit 0403769
Show file tree
Hide file tree
Showing 10 changed files with 1,173 additions and 136 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# rtbox: a rust implemention of toolbx

This aims to be a third implementation of a 'toolbox' runtime. We take inspiration
from the original as well as distrobox and try to combine the image-baked approach
with the feature-completeness of `distrobox` to try to provide with the best of both
worlds: a performant, feature complete toolbox runtime that is self-contained and easy
to install.

## Goals

1. Compatible: we will strive to be compatible with `toolbx` and `distrobox` wherever possible.
2. Performant: we will keep performance at the forefront as we believe that performance can hamper usability and adoption of containerized technologies.
3. Ephemeral: we will enable users to keep ephemeral workflows with a configuration-first mindset
4. Simple: we will keep the application self-contained and easy to use, advanced features should not get in the way of common usage patterns.

## Non-Goals

1. We will not be implementing anything that is not supported by the upstream podman project.
2. We will not implement extensive sandboxing.

## Installation

Currently this project is in **very early stages** of development. It doesn't actually do much of anything but it provides
a toolbx-compatible CLI implementation. We are using rust so to build this project we can easily do it by following these steps:

1. Clone the repo
2. run `./util/cargo build`

The `./util/cargo` script is a wrapper that will fetch cargo from dockerhub and run the build process. It will also create a folder
`${HOME}/.cache/cargo` to cache crates for future builds.

## Usage

This project is currently functionally useless but if you are interested I will leave a sample of the help messages:

```
$ rtbox --help
A rust implementation of toolbx
Usage: rtbox [OPTIONS] <COMMAND>
Commands:
create Create a rtbox container
rm Remove a rtbox container
rmi Remove a rtbox container image
run Execute a command inside a rtbox container
enter Enter into a new shell session inside a rtbox container
list List all rtbox containers
export Export an application, service or binary from a rtbox container to the host
help Print this message or the help of the given subcommand(s)
Options:
-v, --verbose <VERBOSE>
-y, --assume-yes Automatically answer yes to all questions
--log-level <LOG_LEVEL> Set the logging level [default: info] [possible values: info, warn, error, debug, all]
-f, --format <FORMAT> Set the output format [default: human] [possible values: json, human]
--dry-run Do not actually execute API calls
-h, --help Print help information
```

## Acknowledgements

1. Inspiration taken from the [toolbx project](https://github.com/containers/toolbox)
2. A lot of study conducted on [distrobox project](https://github.com/89luca89/distrobox)
3. We naturally depend on the [podman project](https://github.com/containers/podman) for all our container-related needs
4. And many thanks to the [podman-api rust crate](https://crates.io/crates/podman-api) which makes the implementation of this whole thing easier

## License

The code here is released under the GPLv3. You can find a copy of the license [here](https://www.gnu.org/licenses/gpl-3.0.en.html) or in the repo itself.

## FAQ


#### Q: Why creating this?

A: I was interested in learning rust and needed a problem domain where I already had experience. Neither `toolbox` nor `distrobox` fit my workflow quite well so I though I would take a stab at it.

#### Q: What benefits will I get if I use `rtbox` instead?

A: Currently none as there no functionality. In the future I plan to implement features that aren't available in other implementations namely: a configuration file format for quickly replicating toolbox setups and a backup system are on my mind.

#### Q: Configuration file format? What do you mean?

A: This is for my own reference :) - I have some thoughts explaining what I mean on this [issue](https://github.com/containers/toolbox/issues/1018)
54 changes: 34 additions & 20 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use clap::Parser;

mod rtbox{
pub mod core;
pub mod engine;
pub mod error;
pub mod cli;
pub mod config;
pub mod podman;
}

use rtbox::core::{RtBox};
use rtbox::engine::{Engine, RtBox};
use rtbox::error::RtBoxError;
use rtbox::cli::{TboxCli, TboxCommands, Output};

Expand All @@ -16,45 +17,58 @@ use rtbox::cli::{TboxCli, TboxCommands, Output};
async fn main() {

env_logger::init();
rtbox::config::init();

let args = TboxCli::parse();

if let Ok(config) = rtbox::config::init() {

} else {
eprintln!("could not load config");
}
let rtbox_engine = match Engine::new(&"/etc/rtbox.json".to_string()) {
Ok(engine) => engine,
Err(e) => {
eprintln!("{}", e);
panic!("bye");
},
};


let output = match args.command {
TboxCommands::Create { name, image } => {
TboxCommands::Create { name, image, home } => {

let image = image.unwrap_or("fedora:latest".to_string());

if let Ok(tbox) = rtbox::core::create(name, image).await {
if let Ok(tbox) = rtbox_engine.create(&name, &image).await {
Output::Create(tbox)
} else {
Output::Error(RtBoxError{ message: "error creating container".to_string() })
Output::Error(RtBoxError{ error: "error creating container".to_string() })
}
}
TboxCommands::Rm { name, force, all } => {
Output::Error(RtBoxError{ message: "not implemented".to_string() })
if let Ok(tbox_rm_response) = rtbox_engine.rm(name[0].clone(), force, all).await {
Output::Rm(tbox_rm_response)
} else {
Output::Error(RtBoxError{ error: "not implemented".to_string() })
}
}
TboxCommands::List { all } => {

if let Ok(tbox_list) = rtbox::core::list(all.unwrap_or(false)).await {
Output::List(tbox_list)
} else {
Output::Error(RtBoxError{ message: "error listing containers".to_string() })
match rtbox_engine.list(all).await {
Ok(tbox_list) => Output::List(tbox_list),
Err(e) => Output::Error(RtBoxError{ error: e.to_string() }),
}
}
TboxCommands::Run { container, cmd } => {
println!("podman exec -it {:?} {:?}", container, cmd);
Output::Error(RtBoxError{ message: "not implemented".to_string() })
Output::Error(RtBoxError{ error: "not implemented".to_string() })
}
TboxCommands::Enter { name } => {
println!("podman exec -it {:?} /bin/bash -l", name);
Output::Error(RtBoxError{ message: "not implemented".to_string() })
Output::Error(RtBoxError{ error: "not implemented".to_string() })
}
TboxCommands::Export { container, binary, service_unit, application } => {
println!("export {}:{}", container, binary.unwrap_or("null".to_string()));
Output::Error(RtBoxError{ error: "not implemented".to_string() })
}
TboxCommands::Rmi { all, force, image_name } => {
Output::Error(RtBoxError{ error: "not implemented".to_string() })
}
TboxCommands::Init { gid, home, shell } => {
Output::Error(RtBoxError{ error: "not implemented".to_string() })
}
};

Expand Down
98 changes: 94 additions & 4 deletions src/rtbox/cli.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,148 @@
use std::vec::Vec;
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, ValueEnum};
use serde::{Serialize, Deserialize};

use crate::RtBox;
use crate::RtBoxError;

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum TboxCliOutputFormat {
Json,
Human,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum TboxLogLevel {
INFO,
WARN,
ERROR,
DEBUG,
ALL,
}

#[derive(Debug, Parser)]
#[clap(name = "tbox")]
#[clap(about = "A rust implementation of toolbx", long_about = None)]
pub struct TboxCli {
#[clap(short, long)]
verbose: Option<i32>,
/// Automatically answer yes to all questions
#[clap(short, long)]
json: Option<bool>,
#[arg(short = 'y')]
#[arg(default_value_t = false)]
assume_yes: bool,
/// Set the logging level
#[clap(long)]
#[arg(default_value_t = TboxLogLevel::INFO)]
#[arg(value_enum)]
log_level: TboxLogLevel,
/// Set the output format
#[clap(short, long)]
#[arg(value_enum)]
#[arg(default_value_t = TboxCliOutputFormat::Human)]
format: TboxCliOutputFormat,
/// Do not actually execute API calls
#[clap(long)]
#[arg(default_value_t = false)]
dry_run: bool,
/// Subcommand to run
#[command(subcommand)]
pub command: TboxCommands,
}

#[derive(Debug, Subcommand)]
pub enum TboxCommands {
/// Create a rtbox container
#[command(arg_required_else_help = true)]
Create {
/// Container name, will also be used as part of the hostname
name: String,
/// Image to use as base for the container
#[clap(short, long)]
image: Option<String>,
/// Set a custom HOME directory for the container
#[clap(short, long)]
#[arg(short = 'H')]
home: Option<String>,
},
/// Remove a rtbox container
#[command(arg_required_else_help = true)]
Rm {
#[clap(short, long)]
name: String,
/// Container to remove
name: Vec<String>,
/// Remove container even if it is currently running
#[clap(short, long)]
force: Option<bool>,
/// Remove all rtbox containers
#[clap(short, long)]
all: Option<bool>,
},
/// Remove a rtbox container image
#[command(arg_required_else_help = true)]
Rmi {
/// Remove all rtbox container images
#[clap(short, long)]
#[arg(default_value_t = false)]
all: bool,
/// Remove rtbox container images even if running containers are using it
#[clap(short, long)]
#[arg(default_value_t = false)]
force: bool,
/// Name of image to remove
image_name: Option<String>,
},
/// Execute a command inside a rtbox container
#[command(arg_required_else_help = true)]
Run {
/// Container name
#[clap(short, long)]
container: String,
/// Command to execute
cmd: Vec<String>,
},
/// Enter into a new shell session inside a rtbox container
#[command(arg_required_else_help = true)]
Enter {
/// Container to enter into
name: String,
},
/// List all rtbox containers
List {
/// Show all rtbox containers even if they not actively running
#[clap(short, long)]
all: Option<bool>,
},
/// Export an application, service or binary from a rtbox container to the host
#[command(arg_required_else_help = true)]
Export {
/// Container from where to export the application
#[clap(short, long)]
container: String,
/// Path to an executable that will be exported (must exist inside the container)
#[clap(short, long)]
binary: Option<String>,
/// Service unit name that will be exported (must exist inside the container)
#[clap(short, long)]
service_unit: Option<String>,
/// Desktop application name that will be exported (must exist inside the container)
#[clap(short, long)]
application: Option<String>,
},
/// Used to initialize rtbox containers
#[command(arg_required_else_help = true, hide = true)]
Init {
#[clap(long)]
gid: i32,
#[clap(long)]
home: String,
#[clap(long)]
shell: String,
},
}

#[derive(Debug, Serialize, Deserialize)]
pub enum Output {
Create(RtBox),
List(Vec<RtBox>),
Rm(()),
Error(RtBoxError),
}
36 changes: 23 additions & 13 deletions src/rtbox/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,30 @@ pub struct RtBoxConfig {
pub entrypoint: Vec<String>,
}

pub fn init() -> Result<RtBoxConfig, Box<dyn Error>> {
let file_path = Path::new("/etc/rtbox.json");
let file = File::open(file_path)?;
let reader = BufReader::new(file);
impl Default for RtBoxConfig {
fn default() -> Self {
Self {
default_image: "fedora:latest".to_string(),
socket_path: "unix:///run/user/1000/podman/podman.sock".to_string(),
entrypoint: vec![
"/usr/bin/bash".to_string(), "-l".to_string(),
],
}
}
}

impl RtBoxConfig {
pub fn new(config_path: &String) -> Self {
let file_path = Path::new(config_path);
if let Ok(file) = File::open(file_path) {
let reader = BufReader::new(file);

//let config = serde_json::from_reader(reader)?;
let file_config = serde_json::from_reader(reader);

let default_config: RtBoxConfig = RtBoxConfig {
default_image: "fedora:latest".to_string(),
socket_path: "/run/user/1000/podman/podman.sock".to_string(),
entrypoint: vec![
"/usr/bin/bash".to_string(), "-l".to_string(),
],
};

Ok(default_config)
file_config.unwrap_or(Self::default())
} else {
Self::default()
}
}
}
Loading

0 comments on commit 0403769

Please sign in to comment.