Add groups support (#146)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #146
This commit is contained in:
2024-12-06 18:06:01 +00:00
parent aa9222bd22
commit 4c6608bf55
14 changed files with 874 additions and 67 deletions

View File

@ -1,6 +1,7 @@
use crate::app_config::AppConfig;
use crate::utils::time;
use lazy_regex::regex;
use std::collections::HashMap;
use std::fmt::Display;
use std::str::FromStr;
use thiserror::Error;
@ -12,9 +13,105 @@ pub enum VirtWebClientError {
InvalidStatusCode(u16),
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Eq, PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GroupID(String);
impl GroupID {
pub fn route_vm_info(&self) -> String {
format!("/api/group/{}/vm/info", self.0)
}
pub fn route_vm_state(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/state{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_start(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/start{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_shutdown(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/shutdown{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_suspend(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/suspend{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_resume(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/resume{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_kill(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/kill{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_reset(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/reset{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
pub fn route_vm_screenshot(&self, vm: Option<VMUuid>) -> String {
format!(
"/api/group/{}/vm/screenshot{}",
self.0,
match vm {
None => "".to_string(),
Some(id) => format!("?vm_id={}", id.0),
}
)
}
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Hash)]
pub struct VMUuid(Uuid);
#[derive(Default, serde::Deserialize, serde::Serialize)]
pub struct TreatmentResult {
ok: usize,
failed: usize,
}
impl VMUuid {
pub fn route_info(&self) -> String {
format!("/api/vm/{}", self.0)
@ -69,7 +166,7 @@ pub struct TokenClaims {
pub nonce: String,
}
#[derive(serde::Deserialize, Debug)]
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct VMInfo {
pub uuid: VMUuid,
pub name: String,
@ -79,6 +176,18 @@ pub struct VMInfo {
pub number_vcpu: usize,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct VMCaps {
pub can_get_state: bool,
pub can_start: bool,
pub can_shutdown: bool,
pub can_kill: bool,
pub can_reset: bool,
pub can_suspend: bool,
pub can_resume: bool,
pub can_screenshot: bool,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct VMState {
pub state: String,
@ -147,6 +256,16 @@ impl TokenInfo {
false
}
/// List the groups with access
pub fn list_groups(&self) -> Vec<GroupID> {
self.rights
.iter()
.filter(|r| r.verb == "GET")
.filter(|r| regex!("^/api/group/[^/]+/vm/info$").is_match(&r.path))
.map(|r| GroupID(r.path.split("/").nth(3).unwrap().to_string()))
.collect::<Vec<_>>()
}
/// List the virtual machines with access
pub fn list_vm(&self) -> Vec<VMUuid> {
self.rights
@ -168,12 +287,13 @@ async fn request<D: Display>(uri: D) -> anyhow::Result<reqwest::Response> {
let url = format!("{}{}", AppConfig::get().virtweb_base_url, uri);
log::debug!("Will query {uri}...");
let uri = uri.to_string();
let jwt = TokenClaims {
sub: AppConfig::get().virtweb_token_id.to_string(),
iat: time() - 60 * 2,
exp: time() + 60 * 3,
verb: "GET".to_string(),
path: uri.to_string(),
path: uri.split_once('?').map(|s| s.0).unwrap_or(&uri).to_string(),
nonce: Uuid::new_v4().to_string(),
};
let jwt = AppConfig::get().token_private_key().sign_jwt(&jwt)?;
@ -260,6 +380,73 @@ pub async fn vm_screenshot(id: VMUuid) -> anyhow::Result<Vec<u8>> {
.to_vec())
}
/// Get the VM of a group
pub async fn group_vm_info(id: &GroupID) -> anyhow::Result<Vec<VMInfo>> {
json_request(id.route_vm_info()).await
}
/// Get the state of one or all VMs of a group
pub async fn group_vm_state(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<HashMap<VMUuid, String>> {
json_request(id.route_vm_state(vm_id)).await
}
/// Start one or all VMs of a group
pub async fn group_vm_start(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_start(vm_id)).await
}
/// Shutdown one or all VMs of a group
pub async fn group_vm_shutdown(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_shutdown(vm_id)).await
}
/// Kill one or all VMs of a group
pub async fn group_vm_kill(id: &GroupID, vm_id: Option<VMUuid>) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_kill(vm_id)).await
}
/// Reset one or all VMs of a group
pub async fn group_vm_reset(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_reset(vm_id)).await
}
/// Suspend one or all VMs of a group
pub async fn group_vm_suspend(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_suspend(vm_id)).await
}
/// Resume one or all VMs of a group
pub async fn group_vm_resume(
id: &GroupID,
vm_id: Option<VMUuid>,
) -> anyhow::Result<TreatmentResult> {
json_request(id.route_vm_resume(vm_id)).await
}
/// Get the screenshot of one or all VMs of a group
pub async fn group_vm_screenshot(id: &GroupID, vm_id: Option<VMUuid>) -> anyhow::Result<Vec<u8>> {
Ok(request(id.route_vm_screenshot(vm_id))
.await?
.bytes()
.await?
.to_vec())
}
/// Get current server information
pub async fn get_server_info() -> anyhow::Result<SystemInfo> {
json_request("/api/server/info").await