diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53649aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +.idea +*.tar.gz +.vscode +ctr-bundle \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..798176b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,358 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d76c22c9b9b215eeb8d016ad3a90417bd13cb24cf8142756e6472445876cab7" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd1122e63869df2cb309f449da1ad54a7c6dfeb7c7e6ccd8e0825d9eb93bb72" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" + +[[package]] +name = "log" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quark" +version = "0.1.0" +dependencies = [ + "clap", + "flate2", + "log", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..036614f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "quark" +version = "0.1.0" +edition = "2021" +authors = ["Polytech Montpellier - DevOps"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "3.0.5", features = ["derive"] } +flate2 = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0.30" +log = "0.4.16" \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3967287 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.PHONY: build + +build: + cargo build + +pre-test: + @echo "Seting tests environment..." + @mkdir -p /tmp/quark/builds/testquardle/dossier1/ + @touch /tmp/quark/builds/testquardle/fichier1.txt + @touch /tmp/quark/builds/testquardle/fichier2.txt + @touch /tmp/quark/builds/testquardle/dossier1/fichier3.txt + @echo "Done. Your quadle env is named: testquardle" + +test: pre-test + @echo "Running tests..." + cargo run -- build --image sometest --quardle testquardle + mkdir out/testquardle && tar -C out/testquardle -xvf out/testquardle.qrk diff --git a/README.md b/README.md index 8f9a918..dba7367 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ -# Lumper +# Quark + +**quark** is a CLI tool for building and running images for lumper to boot. + +## How to use + +Building a quardle with the container image bundled into the initramfs image: +`$ quark build --image --offline --quardle ` + +Building a quardle with the container image to be pulled from within the guest: +`$ quark build --image --quardle ` + +This commands will create a quardle with the name `.qrk` diff --git a/out/.gitkeep b/out/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/cli/build.rs b/src/cli/build.rs new file mode 100644 index 0000000..c6a583c --- /dev/null +++ b/src/cli/build.rs @@ -0,0 +1,41 @@ +use crate::quardle::Quardle; +use crate::{Handler, Result}; +use clap::Args; + +/// CLI related errors +#[derive(Debug)] +pub enum Error { +} + +/// Arguments for our `BuildCommand`. +/// +/// These arguments are parsed by `clap` and an instance of `BuildCommand` containing +/// arguments is provided. +/// +/// Example : +/// +/// `quark build --image --quardle ` +/// +/// The `handler` method provided below will be executed. +#[derive(Debug, Args)] +pub struct BuildCommand { + /// The url of the container image + #[clap(short, long)] + image: String, + /// The name of the quardle to create + #[clap(short, long)] + quardle: String, + + /// The name of the quardle to create + #[clap(short, long)] + offline: bool +} + +impl Handler for BuildCommand { + fn handler(&self) -> Result<()> { + Quardle::new(self.quardle.clone(), self.image.clone(), self.offline) + .unwrap() + .build(Some(false)).unwrap(); + Ok(()) + } +} \ No newline at end of file diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..d83a4df --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,60 @@ +mod build; + +use crate::cli::build::BuildCommand; +use clap::{Parser, Subcommand}; + +/// CLI related errors +#[derive(Debug)] +pub enum Error { +} + +/// A common result type for our CLI. +pub type Result = std::result::Result; + +/// `Handler` is a trait that should be implemented for each of our commands. +/// +/// It defines the contract & the input / output of a command execution. +pub trait Handler { + /// Executes the command handler. + /// + /// Every command should take no argument, has it is built at runtime with these arguments. + /// Also, a command must always return a `Result<()>`. + fn handler(&self) -> crate::Result<()>; +} + +#[derive(Parser, Debug)] +#[clap(version, author)] +pub struct Cli { + /// Container bundle + #[clap(subcommand)] + pub(crate) command: Command, +} + +impl Cli { + /// Get the command used by the user. + /// + /// For example, if the user executes the command `build`, + /// we dynamically return the command so the `main` can + /// execute it. + pub fn command(self) -> Box { + match self.command { + Command::Build(cmd) => Box::new(cmd), + } + } +} + +/// The enumeration of our commands. +/// +/// Each of our commands should be listed in this enumeration with the following format : +/// CommandName(CommandHandler) +/// +/// Example: +/// +/// You want to add the `list` command: +/// +/// List(ListCommand) +#[derive(Subcommand, Debug)] +pub enum Command { + /// Build a quardle + Build(BuildCommand), +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7f6572e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,14 @@ +use clap::Parser; + +use crate::cli::{Cli, Handler, Result}; + +mod cli; +mod quardle; + +fn main() -> Result<()> { + let cli: Cli = Cli::parse(); + + cli.command().handler()?; + + Ok(()) +} \ No newline at end of file diff --git a/src/quardle/mod.rs b/src/quardle/mod.rs new file mode 100644 index 0000000..d630522 --- /dev/null +++ b/src/quardle/mod.rs @@ -0,0 +1,376 @@ +use std::fs::{File, create_dir_all, remove_dir_all}; +use serde::{Deserialize, Serialize}; +use serde_json; +use std::process::Command; +use log::{info, warn}; + +/// Constances declarations +const QUARK_BUILD_DIR: &str = "/tmp/quark/builds/"; +const QUARK_CONFIG_DIR: &str = "/opt/quark/"; + +const QUARDLE_KERNEL: &str = "vmlinux.bin"; +const QUARDLE_KERNEL_CMDLINE: &str = "/proc/cmdline"; +const QUARDLE_INITRD: &str = "initramfs.img"; + +/// Containers related errors +#[derive(Debug, thiserror::Error)] +pub enum Error {} + +/// A common result type for our module. +pub type Result = std::result::Result; + +#[derive(Serialize, Deserialize)] +struct QuardleConfig { + name: String, + kernel: String, + initrd: String, + cmdline: String, + container_image_url: String, +} + +/// The `Container` struct provides a simple way to +/// create and run a container on the host. +#[derive(Default, Debug, Clone)] +pub struct Quardle { + name: String, + container_image_url: String, + offline: bool +} + +impl Quardle { + + /// Build a quardle from instance variables + /// If delete is true, delete the quardle temporary files after building + pub fn build(&self, delete_after: Option) -> Result<()> { + // creating working directory + create_dir_all(self.clone().get_work_dir()).unwrap(); + + self + .setup() + .add_kernel() + .add_initramfs() + .add_config_file() + .clean_quardle_build_dir() + .make_archive()?; + + // Deleting temporary files used to build the quardle + if delete_after.unwrap_or(false) { + self.delete().unwrap(); + } + + Ok(()) + } + + /// Instanciate a new quardle + pub fn new(name: String, container_image_url: String, offline: bool) -> Option { + Some(Quardle {name, container_image_url, offline}) + } + + /// Delete all temporary files used to create quardle are created at /tmp/quark/builds// + pub fn delete(&self) -> Result<()> { + if !std::path::Path::new(format!("{}", self.clone().get_work_dir()).as_str()).exists() { + remove_dir_all(self.clone().get_work_dir()).unwrap(); + } + Ok(()) + } + + /// Return the path to the quardle working directory + /// /tmp/quark/builds// + /// Used to create temporary files used to build quardle + fn get_work_dir(self) -> String { + format!("{}{}/", QUARK_BUILD_DIR, self.name) + } + + /// Setup default quark configuration + /// Create a `quark` directory in /opt + fn setup(&self) -> &Quardle { + // Create the quark configuration directory + if !std::path::Path::new(&format!("{}", QUARK_CONFIG_DIR)).exists() { + warn!("Quark configuration directory does not exist, creating it !"); + Command::new("mkdir") + .arg("/opt/quark") + .output() + .expect("failed to setup quark"); + } + + // Install kaps sources + if !std::path::Path::new(&format!("{}kaps", QUARK_CONFIG_DIR)).exists() { + info!("Kaps not found, installing it !"); + Command::new("git") + .args(["clone", "https://github.com/virt-do/kaps.git"]) // Using https protocol because it seems not supporting ssh + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to fetch kaps"); + } + + // Install a default kernel configuration file + if !std::path::Path::new(&format!("{}linux-config-x86_64", QUARK_CONFIG_DIR)).exists() { + warn!("Kernel config file not found, installing it !"); + Command::new("curl") + .arg("https://raw.githubusercontent.com/virt-do/lab/main/do-vmm/kernel/linux-config-x86_64") + .arg("-O") + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to fetch kernel config file"); + } + + // Install a script to build kernel + if !std::path::Path::new(&format!("{}mkkernel.sh", QUARK_CONFIG_DIR)).exists() { + warn!("Kernel build script not found, installing it !"); + Command::new("curl") + .arg("https://raw.githubusercontent.com/virt-do/lab/main/do-vmm/kernel/mkkernel.sh") + .arg("-O") + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to fetch kernel build script"); + + Command::new("chmod") + .arg("+x") + .arg(format!("{}mkkernel.sh", QUARK_CONFIG_DIR)) + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to make kernel build script executable"); + } + + // Building kernel binary + if !std::path::Path::new(&format!("{}linux-cloud-hypervisor", QUARK_CONFIG_DIR)).exists() { + warn!("Kernel not builded, building it !"); + Command::new(format!("{}mkkernel.sh", QUARK_CONFIG_DIR)) + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to build kernel"); + } + + // Install a script to build initramfs + if !std::path::Path::new(&format!("{}alpine-minirootfs", QUARK_CONFIG_DIR)).exists() { + warn!("Rootfs not builded, building it !"); + Command::new("curl") + .arg("https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-minirootfs-3.14.2-x86_64.tar.gz") + .arg("-O") + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to download rootfs archive"); + Command::new("mkdir") + .arg("alpine-minirootfs") + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to download initramfs build script"); + Command::new("tar") + .arg("-xzf") + .arg("alpine-minirootfs-3.14.2-x86_64.tar.gz") + .arg("-C") + .arg("alpine-minirootfs") + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to extract rootfs archive"); + + // Adding kaps binary to the rootfs + self.add_kaps_to_rootfs(); + } + + if !std::path::Path::new(&format!("{}mkinitramfs.sh", QUARK_CONFIG_DIR)).exists() { + warn!("InitramFS build script not found, installing it !"); + Command::new("curl") + .arg("https://raw.githubusercontent.com/virt-do/quark/main/tools/mkinitramfs.sh") + .arg("-O") + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to download initramfs build script"); + Command::new("chmod") + .arg("+x") + .arg(format!("{}mkinitramfs.sh", QUARK_CONFIG_DIR)) + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to make initramfs build script executable"); + } + + // Install a script to build kaps bundle + if !std::path::Path::new(&format!("{}mkbundle.sh", QUARK_CONFIG_DIR)).exists() { + warn!("Kaps bundle build script not found, installing it !"); + Command::new("curl") + .arg("https://raw.githubusercontent.com/virt-do/lab/main/do-vmm/rootfs/mkbundle.sh") + .arg("-O") + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to fetch kaps bundle build script"); + Command::new("chmod") + .arg("+x") + .arg(format!("{}mkbundle.sh", QUARK_CONFIG_DIR)) + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to make kernel build script executable"); + } + self + } + + /// Append kernel configuration + /// Fetch automated script if isn't already installed, and use some bash script to build it + fn add_kernel(&self) -> &Quardle { + info!("Installing kernel binary !"); + Command::new("cp") + .arg(format!("{}linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin", QUARK_CONFIG_DIR)) + .arg(format!("{}", self.clone().get_work_dir())) + .spawn() + .expect("failed to copy kernel"); + self + } + + /// Append basic rootfs + /// Fetch automated script if isn't already installed, and use some bash script to build it + fn add_initramfs(&self) -> &Quardle { + info!("Installing initRamFS image to quardle"); + Command::new("cp") + .arg("-r") + .arg(format!("{}alpine-minirootfs", QUARK_CONFIG_DIR)) + .arg(format!("{}", self.clone().get_work_dir())) + .spawn() + .expect("failed to copy rootfs"); + + // If offline mode is active, we need to build kaps bundle image directly in the quardle. + if self.offline { + info!("Offline mode, adding kaps bundle to the quardle."); + Command::new(format!("{}mkbundle.sh", QUARK_CONFIG_DIR)) + .arg(format!("{}/alpine-minirootfs/ctr-bundle", self.clone().get_work_dir())) + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to build initramfs"); + } + + // add init file to it + info!("InitramFS not builded, building it !"); + Command::new(format!("{}mkinitramfs.sh", QUARK_CONFIG_DIR)) + .arg(format!("{}/alpine-minirootfs", self.clone().get_work_dir())) + .current_dir(format!("{}", QUARK_CONFIG_DIR)) + .output() + .expect("failed to build initramfs"); + + self + } + + fn clean_quardle_build_dir(&self) -> &Quardle { + info!("Cleaning quardle build directory !"); + Command::new("rm") + .arg("-rdf") + .arg("alpine-minirootfs") + .arg("mkbundle.sh") + .arg("mkinitramfs.sh") + .current_dir(format!("{}", self.clone().get_work_dir())) + .output() + .expect("failed to clean quardle build directory"); + self + } + + /// Append kaps binary to rootfs image + /// Fetch kaps source code if isn't already installed, build it from source and copy it to the working directory + fn add_kaps_to_rootfs(&self) -> &Quardle { + info!("Installing kaps to quardle"); + Command::new("cargo") + .current_dir(format!("{}kaps", QUARK_CONFIG_DIR)) + .arg("build") + .arg("--release") + // .arg("--out-dir") //TODO: outdir is only available on nightly for now, should be used later + // .arg(format!("{}/rootfs/usr/bin/kaps",self.clone().get_work_dir())) + .output() + .expect("failed to build kaps"); + + Command::new("cp") + .arg(format!("{}kaps/target/release/kaps", QUARK_CONFIG_DIR)) + .arg(format!("{}/alpine-minirootfs/usr/bin/kaps",self.clone().get_work_dir())) + .output() + .expect("failed to copy kaps"); + + self + } + + /// Generate the config file of the quardle + /// The config file is a JSON file containing a QuardleConfig struct + fn add_config_file(&self) -> &Quardle { + + let config = QuardleConfig { + name: self.name.clone(), + kernel: QUARDLE_KERNEL.to_string(), + initrd: QUARDLE_INITRD.to_string(), + cmdline: QUARDLE_KERNEL_CMDLINE.to_string(), + container_image_url: self.container_image_url.clone() + }; + + let config_json = serde_json::to_string(&config).unwrap(); + let mut file = File::create(format!("{}quark.json",self.clone().get_work_dir())).unwrap(); + + use std::io::Write; + file.write_all(config_json.as_bytes()).unwrap(); + + self + } + + /// Create compressed archive from quardle files and append it to /out/.qrk + fn make_archive(&self) -> Result<()> { + info!("Packaging quardle."); + Command::new("tar") + .arg("-zcvf") + .arg(format!("{}",format!("out/{}.qrk",self.name))) + .arg(format!("{}",self.clone().get_work_dir())) + .output() + .expect("failed to create archive"); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn quardle_new() { + let quardle = Quardle::new("test1".to_string(), "container1".to_string(), false); + assert_eq!(quardle.as_ref().unwrap().name, "test1"); + assert_eq!(quardle.as_ref().unwrap().container_image_url, "container1"); + } + + #[test] + fn quardle_get_work_dir() { + let quardle = Quardle::new("this-should-be-a-directory".to_string(), "my-container".to_string(), false); + assert_eq!(quardle.unwrap().get_work_dir(), "/tmp/quark/builds/this-should-be-a-directory/"); + } + + #[test] + fn quardle_setup() { + let quardle = Quardle::new("test2".to_string(), "container2".to_string(), false); + quardle.as_ref().unwrap().setup(); + assert_eq!(quardle.as_ref().unwrap().name, "test2"); + assert_eq!(quardle.as_ref().unwrap().container_image_url, "container2"); + } + + #[test] + fn quardle_add_kernel() { + let quardle = Quardle::new("test3".to_string(), "container3".to_string(), false); + quardle.as_ref().unwrap().add_kernel(); + assert_eq!(quardle.as_ref().unwrap().name, "test3"); + assert_eq!(quardle.as_ref().unwrap().container_image_url, "container3"); + } + + #[test] + fn quardle_add_initramfs() { + let quardle = Quardle::new("test4".to_string(), "container4".to_string(), false); + quardle.as_ref().unwrap().add_initramfs(); + assert_eq!(quardle.as_ref().unwrap().name, "test4"); + assert_eq!(quardle.as_ref().unwrap().container_image_url, "container4"); + } + + #[test] + fn quardle_add_kaps_to_rootfs() { + let quardle = Quardle::new("test6".to_string(), "container6".to_string(), false); + quardle.as_ref().unwrap().add_initramfs().add_kaps_to_rootfs(); + assert_eq!(quardle.as_ref().unwrap().name, "test6"); + assert_eq!(quardle.as_ref().unwrap().container_image_url, "container6"); + } + + #[test] + #[should_panic] + fn quardle_clean_quardle_build_dir() { + let quardle = Quardle::new("test5".to_string(), "container5".to_string(), false); + quardle.as_ref().unwrap().clean_quardle_build_dir(); + assert_eq!(quardle.as_ref().unwrap().name, "test5"); + assert_eq!(quardle.as_ref().unwrap().container_image_url, "container5"); + } +} \ No newline at end of file diff --git a/tools/mkinitramfs.sh b/tools/mkinitramfs.sh new file mode 100644 index 0000000..df975ba --- /dev/null +++ b/tools/mkinitramfs.sh @@ -0,0 +1,26 @@ +#!/usr/bin/bash +DEST=${1:-"alpine-minirootfs"} + +pushd "$DEST" +cat > init < ../initramfs.img + +popd \ No newline at end of file