Setup database

This commit is contained in:
2024-11-03 19:24:00 +00:00
parent 90abd08088
commit f7279c8707
11 changed files with 1842 additions and 92 deletions

87
src/db.rs Normal file
View File

@ -0,0 +1,87 @@
use chrono::{DateTime, Utc};
/// Users who buy VM's
pub struct User {
/// Unique ID of this user (database generated)
pub id: u64,
/// The nostr public key for this user
pub pubkey: [u8; 32],
/// When this user first started using the service (first login)
pub created: DateTime<Utc>,
}
/// The type of VM host
pub enum VmHostKind {
Proxmox,
}
/// A VM host
pub struct VmHost {
pub id: u64,
pub kind: VmHostKind,
pub name: String,
pub ip: String,
/// Total number of CPU cores
pub cpu: u16,
/// Total memory size in bytes
pub memory: u64,
/// If VM's should be provisioned on this host
pub enabled: bool,
pub api_token: String,
}
pub struct VmHostDisk {
pub id: u64,
pub host_id: u64,
pub name: String,
pub size: u64,
pub kind: DiskType,
pub interface: DiskInterface,
pub enabled: bool,
}
pub enum DiskType {
HDD,
SSD,
}
pub enum DiskInterface {
SATA,
SCSI,
PCIe,
}
pub struct VmOsImage {
pub id: u64,
pub name: String,
pub enabled: bool,
}
pub struct IpRange {
pub id: u64,
pub cidr: String,
pub enabled: bool,
}
pub struct Vm {
/// Unique VM ID (Same in proxmox)
pub id: u64,
/// The host this VM is on
pub host_id: u64,
/// The user that owns this VM
pub user_id: u64,
/// The base image of this VM
pub image_id: u64,
/// When the VM was created
pub created: DateTime<Utc>,
/// When the VM expires
pub expires: DateTime<Utc>,
/// How many vCPU's this VM has
pub cpu: u16,
/// How much RAM this VM has in bytes
pub memory: u64,
/// How big the disk is on this VM in bytes
pub disk_size: u64,
/// The [VmHostDisk] this VM is on
pub disk_id: u64,
}

View File

@ -1,32 +1,26 @@
use config::{Config, File};
use log::info;
use crate::proxmox::{Client, VersionResponse};
use crate::provisioner::Provisioner;
use crate::settings::Settings;
use config::{Config, File};
use sqlx::MySqlPool;
mod settings;
mod db;
mod provisioner;
mod proxmox;
mod settings;
mod vm;
#[tokio::main]
#[rocket::main]
async fn main() -> Result<(), anyhow::Error> {
pretty_env_logger::init();
let config: Settings = Config::builder()
.add_source(File::with_name("config.toml"))
.build()?.try_deserialize()?;
.build()?
.try_deserialize()?;
let client = Client::new(config.server.parse()?)
.with_api_token(
&config.user,
&config.realm,
&config.token_id,
&config.secret,
);
let db = MySqlPool::connect(&config.db).await?;
sqlx::migrate!("./migrations").run(&db).await?;
let provisioner = Provisioner::new(db.clone());
let nodes = client.list_nodes().await.expect("Error listing nodes");
for n in &nodes {
let vms = client.list_vms(&n.name).await?;
for vm in &vms {
}
}
Ok(())
}

19
src/provisioner.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::db;
use crate::vm::VMSpec;
use anyhow::Error;
use sqlx::MySqlPool;
pub struct Provisioner {
db: MySqlPool,
}
impl Provisioner {
pub fn new(db: MySqlPool) -> Self {
Self { db }
}
/// Provision a new VM
pub async fn provision(&self, spec: VMSpec) -> Result<db::Vm, Error> {
todo!()
}
}

View File

@ -1,10 +1,8 @@
use std::fmt::Debug;
use std::ops::Deref;
use anyhow::Error;
use log::info;
use reqwest::{Body, ClientBuilder, Request, RequestBuilder, Url};
use serde::{Deserialize, Deserializer, Serialize};
use reqwest::{Body, ClientBuilder, Url};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::Debug;
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ClientToken {
@ -23,7 +21,8 @@ impl Client {
pub fn new(base: Url) -> Self {
let mut client = ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build().expect("Failed to build client");
.build()
.expect("Failed to build client");
Self {
base,
@ -54,38 +53,50 @@ impl Client {
Ok(rsp.data)
}
pub async fn list_vms(&self, node: &str) -> Result<Vec<VmInfo>, Error> {
let rsp: ResponseBase<Vec<VmInfo>> = self.get(&format!("/api2/json/nodes/{}/qemu", node)).await?;
pub async fn list_vms(&self, node: &str, full: bool) -> Result<Vec<VmInfo>, Error> {
let rsp: ResponseBase<Vec<VmInfo>> =
self.get(&format!("/api2/json/nodes/{node}/qemu")).await?;
Ok(rsp.data)
}
async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
let rsp = self.client
Ok(self
.client
.get(self.base.join(path)?)
.header("Authorization", format!("PVEAPIToken={}", self.token.password))
.send().await?
.error_for_status()?;
let text = rsp.text().await?;
info!("{}->{}", path, text);
Ok(serde_json::from_str(&text)?)
.header(
"Authorization",
format!("PVEAPIToken={}", self.token.password),
)
.send()
.await?
.json::<T>()
.await
.map_err(|e| Error::new(e))?)
}
async fn post<T: DeserializeOwned, R: Into<Body>>(&self, path: &str, body: R) -> Result<T, Error> {
Ok(
self.client
.post(self.base.join(path)?)
.header("Authorization", format!("PVEAPIToken={}", self.token.password))
.body(body)
.send().await?
.error_for_status()?
.json().await?
)
async fn post<T: DeserializeOwned, R: Into<Body>>(
&self,
path: &str,
body: R,
) -> Result<T, Error> {
Ok(self
.client
.post(self.base.join(path)?)
.header(
"Authorization",
format!("PVEAPIToken={}", self.token.password),
)
.body(body)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
}
#[derive(Deserialize)]
pub struct ResponseBase<T>
{
pub struct ResponseBase<T> {
pub data: T,
}
@ -132,4 +143,12 @@ pub struct VmInfo {
pub status: VmStatus,
#[serde(rename = "vmid")]
pub vm_id: i32,
}
pub cpus: Option<u16>,
#[serde(rename = "maxdisk")]
pub max_disk: Option<u64>,
#[serde(rename = "maxmem")]
pub max_mem: Option<u64>,
pub name: Option<String>,
pub tags: Option<String>,
pub uptime: Option<u64>,
}

View File

@ -2,9 +2,5 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct Settings {
pub server: String,
pub user: String,
pub realm: String,
pub token_id: String,
pub secret: String,
}
pub db: String,
}

11
src/vm.rs Normal file
View File

@ -0,0 +1,11 @@
pub enum DiskType {
SSD,
HDD,
}
pub struct VMSpec {
pub cpu: u16,
pub memory: u64,
pub disk: u64,
pub disk_type: DiskType,
}