From 0b16a8069fddfdc01ae75f86caf994318bcbe458 Mon Sep 17 00:00:00 2001 From: Aleksandr Berkuta Date: Mon, 23 Jun 2025 19:15:34 +0300 Subject: [PATCH] Remove unused code --- Cargo.toml | 1 + Readme.md | 114 ++++++++++++++++++++ src/cli.rs | 2 - src/config.rs | 207 ------------------------------------- src/handlers/boot.rs | 2 - src/handlers/cloud-init.rs | 0 src/inventory.rs | 14 ++- src/main.rs | 30 +----- 8 files changed, 130 insertions(+), 240 deletions(-) create mode 100644 Readme.md delete mode 100644 src/config.rs create mode 100644 src/handlers/cloud-init.rs diff --git a/Cargo.toml b/Cargo.toml index 64c8751..f859e65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ mac_address2 = "2" [profile.release] target = "x86_64-unknown-linux-musl" +opt-level = "z" [profile.dev] target = "x86_64-unknown-linux-musl" diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..c03a695 --- /dev/null +++ b/Readme.md @@ -0,0 +1,114 @@ +# Orpheus + +Orpheus is a backend for a [pixiecore](https://github.com/danderson/netboot/blob/main/pixiecore/README.md) API mode. It provides boot configurations per host with specified MAC-address. + +## Enpoint Description + +Orpheus usppose to respond on: + +GET /v1/boot/{mac_address} + +with a JSON: + +```json +{ + "kernel": "https://files.local/kernel", + "initrd": ["https://files.local/initrd"], + "cmdline": "selinux=1 coreos.autologin", + "message": "Booting host" +} +``` + +according [pixiecore API specification](https://github.com/danderson/netboot/blob/main/pixiecore/README.api.md): + + kernel (string): the URL of the kernel to boot. + initrd (list of strings): URLs of initrds to load. The kernel will flatten all the initrds into a single filesystem. + cmdline (string): commandline parameters for the kernel. The commandline is processed by Go's text/template library. Within the template, a URL function is available that takes a URL and rewrites it such that Pixiecore proxies the request. + message (string): A message to display before booting the provided configuration. Note that displaying this message is on a best-effort basis only, as particular implementations of the boot process may not support displaying text. + +## Server Configuration + +Orpheus using yaml file for a inventory description: + +```yaml +webserver: "http://my.webserver.net" +hosts: + "aa:bb:cc:dd:ee:ff": &test + name: my_pxe_booted_host + version: version_of_kernel + type: type_of_kernel + cmdline: + - simple_string + - apkovl: path_to_apkovl_on_webserver.tar.gz + - modloop + - ds: path_to_data_source_on_webserver + + "aa:bb:cc:dd:ee:00": + <<: *test + name: test2 +``` + +To boot alpine linux in diskless mode is required three files: + +- kernel +- initrd +- modloop + +All three lies in one directory on the server, to boot succesfully you have to boot +kernel with the same version of initrd and modloop. So it is convinient to setup just +version and type of the desired kernel. + +`webserver` - is http webserver in local network which serves file to a booting host. +`hosts` - is a map which contains mac addresses of the booting machines as a keys +and booting parameters as value. +`version` in the host patameter directs to the +kernel-version directory, +`type` - describes wich type of the file to request, usually it is `lts`, or `virt`. + +Webserver suppose to have following directory structure: +In the root there is 3 types of directories - kernel directory, directory with name `apkovl` and `tiny-cloud`. +`kernel` directory can have any name (suppose to have name related to kernel version) and contain kernel, initrd and modloop files. +`apkovl` directory contain aplovl files. +`tini-cloud` directory contains subdirectories with files related to tiniy-cloud config files. + +Here is an example + +``` +/ +├── alpine-3.21 # kernel directory for alpine linux kernel `version` 3.21 +│ ├── initramfs-lts # initrd with `lts` type +│ ├── initramfs-virt # initrd with `virt` is type +│ ├── modloop-lts +│ ├── modloop-virt +│ ├── vmlinuz-lts # kernel +│ └── vmlinuz-virt +├── alpine-3.19 # kernel directory for alpine linux version 3.19 +│ ├── initramfs-lts +│ ├── initramfs-virt +│ ├── modloop-lts +│ ├── modloop-virt +│ ├── vmlinuz-lts +│ └── vmlinuz-virt +├── apkovl +│ ├── my_apkovl.tar.gz +│ └── backup.apkovl.tar.gz +└── tiny-cloud + ├── server # contains tiny-cloud config files for a server node + │ ├── meta-data + │ ├── network-config + │ ├── user-data + │ └── vendor-data + └── agent # contains tiny-cloud config files for agent node + ├── meta-data + ├── network-config + ├── user-data + └── vendor-data +``` + +## Usage + +Run: + +```bash +orpheus -i inventory.yaml server +``` diff --git a/src/cli.rs b/src/cli.rs index a411f95..add0fa2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,5 @@ use clap::{Parser, Subcommand}; -use log::Level; - #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] pub struct Cli { diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index eaaa4fa..0000000 --- a/src/config.rs +++ /dev/null @@ -1,207 +0,0 @@ -use crate::Inventory; -use figment::{ - providers::{Format, YamlExtended}, - Error as FigError, Figment, -}; -use log::{error, info, warn}; -use mac_address2::MacAddress; -use serde_json::json; -use std::collections::HashMap; -use std::fs::File; -use tera::Tera; - -use std::io::Read; - -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - // #[error("Template error: {0}")] - #[error(transparent)] - Template(#[from] tera::Error), - #[error("Teplate result error: {0}")] - TemplateResult(#[from] serde_json::Error), - #[error("Inventory parsing error: {0}")] - InventorySerialization(#[from] FigError), - #[error("Inventory hosts error: {0}")] - InventoryHosts(String), - #[error("Inventory read error: {0}")] - InventoryHostsFileOpen(#[from] std::io::Error), -} - -pub fn load_inventory(inventory_file: String) -> Result { - // let file = File::open(&inventory_file).map_err(|e| Error::InventoryHostsFileOpen(e))?; - - let inventory: Inventory = Figment::new() - .merge(YamlExtended::file(inventory_file)) - .extract() - .map_err(|e| Error::InventorySerialization(e))?; - - return Ok(inventory); -} - -// pub fn parse_inventory(inventory_reader: R) -> Result { -// let inventory: Inventory = YamlExtended::string(inventory_reader) -// .extract() -// .map_err(|e| Error::InventorySerialization(e))?; -// -// // Validate hosts -// if inventory.hosts.is_empty() { -// return Err(Error::InventoryHosts( -// "The 'hosts' entry is empty".to_string(), -// )); -// } -// -// // Validate each host entry -// for (mac, _) in &inventory.hosts { -// let _ = mac.parse::().map_err(|e| { -// Error::InventoryHosts(format!( -// "The 'hosts' entry contains invalid MAC address: {}, error: {}", -// &mac, e -// )) -// })?; -// } -// -// Ok(inventory) -// } - -pub fn validate_template(inventory: &Inventory, tera: &Tera) -> Result<(), Error> { - for (_, host) in &inventory.hosts { - let context = tera::Context::from_serialize(json!( - { "webserver": &inventory.webserver, - "host": host - } - )) - .map_err(|e| Error::Template(e))?; - - // Try rendering the template for this host - let rendered_text = tera - .render("boot", &context) - .map_err(|e| Error::Template(e))?; - - warn!("{}", &rendered_text); - warn!("json rendered: {:#?}", json!(&rendered_text)); - - let _: serde_json::Value = - serde_json::from_str(&rendered_text).map_err(|e| Error::TemplateResult(e))?; - } - Ok(()) -} - -// use serde::{Deserialize, Serialize}; - -// #[derive(Debug)] -// struct Inv<'a> { -// data: saphyr::Yaml<'a>, -// } -// -// fn parse_inventory2<'a, R: Read>(yaml_str: &'a str) -> Result, Error> { -// let yaml_str = std::fs::read_to_string("inventory.yaml")?; -// let inventory: Yaml = Yaml::load_from_str(&yaml_str).unwrap()[0].clone(); -// // let mut decoder = YamlDecoder::read(yaml_str.as_bytes()); -// // let out = decoder.decode().map_err(|e| Error::InventoryParse(e))?; -// -// // let inventory: Inventory = out -// println!("{:#?}", inventory); -// return unimplemented!(); -// } - -#[cfg(test)] -mod tests { - use super::*; - use tera::Tera; - - // #[test] - // fn valid_inventory() { - // let inventory = r#" - // default: - // key: value - // hosts: - // aa:bb:cc:dd:ee:ff: - // key: value - // aa-bb-cc-dd-ee-ff: - // host_key: host2_value - // "#; - // let result = parse_inventory(inventory.as_bytes()); - // result.unwrap(); - // } - // - // #[test] - // fn invalid_inventory() { - // let inventory = r#" - // default: - // key: value - // hosts: - // "#; - // assert!(parse_inventory(inventory.as_bytes()).is_err()); - // } - // - // #[test] - // fn invalid_host() { - // let inventory = r#" - // default: - // key: value - // hosts: - // my_test_host: - // key: value - // "#; - // assert!(parse_inventory(inventory.as_bytes()).is_err()); - // } - // - // #[test] - // fn valid_template() { - // let inventory_str = r#" - // default: - // key1: value1 - // hosts: - // aa:bb:cc:dd:ee:ff: - // host_key: host_value - // "#; - // let inventory: Inventory = serde_yaml_bw::from_str(inventory_str).unwrap(); - // let template = r#" - // { - // "host": "{{ host.host_key }}", - // "default": "{{ default.key1 }}" - // } - // "#; - // let mut tera = Tera::default(); - // tera.add_raw_templates(vec![("boot", template)]).unwrap(); - // validate_template(&inventory, &tera).unwrap(); - // } - // - // #[test] - // fn template_unexisted_variable_render() { - // let inventory_str = r#" - // default: - // key1: value1 - // hosts: - // aa:bb:cc:dd:ee:ff: - // host_key: host_value - // "#; - // let inventory: Inventory = serde_yaml_bw::from_str(inventory_str).unwrap(); - // let template = r#" - // "{{ unexisted.variable }}", - // "#; - // let mut tera = Tera::default(); - // tera.add_raw_templates(vec![("boot", template)]).unwrap(); - // assert!(validate_template(&inventory, &tera).is_err()); - // } - // - // #[test] - // fn template_result_invalid_json() { - // let inventory_str = r#" - // default: - // key1: value1 - // hosts: - // aa:bb:cc:dd:ee:ff: - // host_key: host1_value - // "#; - // let inventory: Inventory = serde_yaml_bw::from_str(inventory_str).unwrap(); - // let template = r#" - // Not a valid JSON string containing variable: {{ host.host_key }} - // "#; - // let mut tera = Tera::default(); - // tera.add_raw_templates(vec![("boot", template)]).unwrap(); - // assert!(validate_template(&inventory, &tera).is_err()); - // } -} diff --git a/src/handlers/boot.rs b/src/handlers/boot.rs index bb48b87..c54fb46 100644 --- a/src/handlers/boot.rs +++ b/src/handlers/boot.rs @@ -1,10 +1,8 @@ use crate::inventory::Inventory; use actix_web::{web, HttpResponse, Responder}; -use log::{error, info, warn}; use serde_json::json; use std::sync::Arc; -use tera::Tera; pub async fn boot_handler( mac: web::Path, diff --git a/src/handlers/cloud-init.rs b/src/handlers/cloud-init.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/inventory.rs b/src/inventory.rs index fedf772..577e51d 100644 --- a/src/inventory.rs +++ b/src/inventory.rs @@ -1,7 +1,9 @@ +use figment::{ + providers::{Format, YamlExtended}, + Error as FigError, Figment, +}; use serde::de::{self, Deserializer, MapAccess, Visitor}; use serde::{Deserialize, Serialize}; -use serde_yaml_bw::Value as YamlValue; -use std::cmp; use std::collections::HashMap; use std::fmt; @@ -135,6 +137,14 @@ impl<'de> Deserialize<'de> for CmdParams { } } +pub fn load_inventory(inventory_file: String) -> Result { + let inventory: Inventory = Figment::new() + .merge(YamlExtended::file(inventory_file)) + .extract()?; + + return Ok(inventory); +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/main.rs b/src/main.rs index 48fabbe..88eb5f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,15 @@ mod cli; -mod config; mod handlers; mod inventory; use crate::cli::*; -use crate::config::{load_inventory, validate_template}; use crate::handlers::boot::boot_handler; -use crate::inventory::Inventory; +use crate::inventory::{load_inventory, Inventory}; use actix_web::{web, App, HttpServer}; use env_logger; -use log::{error, info, warn}; -use std::error::Error; +use log::error; use std::sync::Arc; -use tera::Tera; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -21,6 +17,7 @@ async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", cli.loging); env_logger::init(); // Initialize the logger + let inventory: Inventory = match load_inventory(cli.inventory) { Ok(inv) => inv, Err(e) => { @@ -29,29 +26,9 @@ async fn main() -> std::io::Result<()> { } }; - // create template engine instance - let mut tera = Tera::default(); - match tera.add_template_file("templates/boot.json.tmpl", Some("boot")) { - Ok(_) => {} - Err(e) => { - error!( - "Error loading template: {}, reason:\n{}", - e, - e.source().unwrap() - ); - std::process::exit(1); - } - }; - - if let Err(e) = validate_template(&inventory, &tera) { - error!("Template validation error: {}, {:#?}", e, e.source()); - std::process::exit(1); - } - // wrap objects into Atomic Reference Counter to use in multithead // Actix environment let inventory = Arc::new(inventory); - let tera = Arc::new(tera); match cli.command { Commands::Check => { @@ -62,7 +39,6 @@ async fn main() -> std::io::Result<()> { return HttpServer::new(move || { App::new() .app_data(web::Data::new(inventory.clone())) - .app_data(web::Data::new(tera.clone())) .route("/v1/boot/{mac}", web::get().to(boot_handler)) }) .bind((cli.address, cli.port))?