Remove unused code
This commit is contained in:
parent
60cd7b5c81
commit
0b16a8069f
@ -20,6 +20,7 @@ mac_address2 = "2"
|
|||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
target = "x86_64-unknown-linux-musl"
|
target = "x86_64-unknown-linux-musl"
|
||||||
|
opt-level = "z"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
target = "x86_64-unknown-linux-musl"
|
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 clap::{Parser, Subcommand};
|
||||||
|
|
||||||
use log::Level;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
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 crate::inventory::Inventory;
|
||||||
|
|
||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use log::{error, info, warn};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tera::Tera;
|
|
||||||
|
|
||||||
pub async fn boot_handler(
|
pub async fn boot_handler(
|
||||||
mac: web::Path<String>,
|
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::de::{self, Deserializer, MapAccess, Visitor};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml_bw::Value as YamlValue;
|
|
||||||
use std::cmp;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
30
src/main.rs
30
src/main.rs
@ -1,19 +1,15 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod inventory;
|
mod inventory;
|
||||||
|
|
||||||
use crate::cli::*;
|
use crate::cli::*;
|
||||||
use crate::config::{load_inventory, validate_template};
|
|
||||||
use crate::handlers::boot::boot_handler;
|
use crate::handlers::boot::boot_handler;
|
||||||
use crate::inventory::Inventory;
|
use crate::inventory::{load_inventory, Inventory};
|
||||||
|
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use env_logger;
|
use env_logger;
|
||||||
use log::{error, info, warn};
|
use log::error;
|
||||||
use std::error::Error;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tera::Tera;
|
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
@ -21,6 +17,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
std::env::set_var("RUST_LOG", cli.loging);
|
std::env::set_var("RUST_LOG", cli.loging);
|
||||||
|
|
||||||
env_logger::init(); // Initialize the logger
|
env_logger::init(); // Initialize the logger
|
||||||
|
|
||||||
let inventory: Inventory = match load_inventory(cli.inventory) {
|
let inventory: Inventory = match load_inventory(cli.inventory) {
|
||||||
Ok(inv) => inv,
|
Ok(inv) => inv,
|
||||||
Err(e) => {
|
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
|
// wrap objects into Atomic Reference Counter to use in multithead
|
||||||
// Actix environment
|
// Actix environment
|
||||||
let inventory = Arc::new(inventory);
|
let inventory = Arc::new(inventory);
|
||||||
let tera = Arc::new(tera);
|
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Check => {
|
Commands::Check => {
|
||||||
@ -62,7 +39,6 @@ async fn main() -> std::io::Result<()> {
|
|||||||
return HttpServer::new(move || {
|
return HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.app_data(web::Data::new(inventory.clone()))
|
.app_data(web::Data::new(inventory.clone()))
|
||||||
.app_data(web::Data::new(tera.clone()))
|
|
||||||
.route("/v1/boot/{mac}", web::get().to(boot_handler))
|
.route("/v1/boot/{mac}", web::get().to(boot_handler))
|
||||||
})
|
})
|
||||||
.bind((cli.address, cli.port))?
|
.bind((cli.address, cli.port))?
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user