Remove unused code
This commit is contained in:
parent
60cd7b5c81
commit
0b16a8069f
@ -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"
|
||||
|
||||
114
Readme.md
Normal file
114
Readme.md
Normal file
@ -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 <server_url>/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
|
||||
```
|
||||
@ -1,7 +1,5 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use log::Level;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
|
||||
207
src/config.rs
207
src/config.rs
@ -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<Inventory, Error> {
|
||||
// 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<R: Read>(inventory_reader: R) -> Result<Inventory, Error> {
|
||||
// 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::<MacAddress>().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<Inv<'a>, 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());
|
||||
// }
|
||||
}
|
||||
@ -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<String>,
|
||||
|
||||
0
src/handlers/cloud-init.rs
Normal file
0
src/handlers/cloud-init.rs
Normal file
@ -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<Inventory, FigError> {
|
||||
let inventory: Inventory = Figment::new()
|
||||
.merge(YamlExtended::file(inventory_file))
|
||||
.extract()?;
|
||||
|
||||
return Ok(inventory);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
30
src/main.rs
30
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))?
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user