Setup custom api client

This commit is contained in:
Kieran 2024-11-03 15:43:45 +00:00
parent 6e6a589f38
commit 90abd08088
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
6 changed files with 580 additions and 555 deletions

950
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
http = "1.1.0"
proxmox-client = { git = "git://git.proxmox.com/git/proxmox.git", branch = "master", features = ["hyper-client"] }
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
anyhow = "1.0.83"
log = "0.4.21"
config = { version = "0.14.0", features = ["toml"] }
pretty_env_logger = "0.5.0"
serde = { version = "1.0.213", features = ["derive"] }
reqwest = { version = "0.12.8", features = ["json"] }
serde_json = "1.0.132"

View File

@ -1,3 +1,5 @@
server = "10.97.0.234"
token_id = "root@pam!test-dev"
server = "https://10.97.0.234:8006/"
user = "root"
realm = "pam"
token_id = "test-dev"
secret = "e2d8d39f-63ce-48f0-a025-b428d29a26e3"

View File

@ -1,33 +1,32 @@
use std::default;
use config::{Config, ConfigBuilder};
use config::{Config, File};
use log::info;
use proxmox_client::{AuthenticationKind, HttpApiClient, TlsOptions, Token};
use crate::proxmox::{Client, VersionResponse};
use crate::settings::Settings;
mod settings;
mod proxmox;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
pretty_env_logger::init();
let config: Settings = Config::builder()
.add_source("config.toml")
.add_source(File::with_name("config.toml"))
.build()?.try_deserialize()?;
let client = proxmox_client::Client::with_options(
config.server,
TlsOptions::Insecure,
Default::default())?;
let client = Client::new(config.server.parse()?)
.with_api_token(
&config.user,
&config.realm,
&config.token_id,
&config.secret,
);
client.set_authentication(AuthenticationKind::Token(Token {
userid: config.token_id.clone(),
prefix: "PVEAPIToken".to_string(),
value: config.secret.clone(),
perl_compat: false,
}));
let rsp = client.get("/api2/json/version").await?;
let string = String::from_utf8(rsp.body)?;
info!("Version: {}", string);
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(())
}

135
src/proxmox.rs Normal file
View File

@ -0,0 +1,135 @@
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 serde::de::DeserializeOwned;
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ClientToken {
username: String,
realm: String,
password: String,
}
pub struct Client {
base: Url,
token: ClientToken,
client: reqwest::Client,
}
impl Client {
pub fn new(base: Url) -> Self {
let mut client = ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build().expect("Failed to build client");
Self {
base,
token: ClientToken::default(),
client,
}
}
pub fn with_api_token(mut self, user: &str, realm: &str, token_id: &str, secret: &str) -> Self {
// PVEAPIToken=USER@REALM!TOKENID=UUID
self.token = ClientToken {
username: user.to_string(),
realm: realm.to_string(),
password: format!("{}@{}!{}={}", user, realm, token_id, secret),
};
self
}
/// Get version info
pub async fn version(&self) -> Result<VersionResponse, Error> {
let rsp: ResponseBase<VersionResponse> = self.get("/api2/json/version").await?;
Ok(rsp.data)
}
/// List nodes
pub async fn list_nodes(&self) -> Result<Vec<NodeResponse>, Error> {
let rsp: ResponseBase<Vec<NodeResponse>> = self.get("/api2/json/nodes").await?;
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?;
Ok(rsp.data)
}
async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
let rsp = 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)?)
}
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 data: T,
}
#[derive(Deserialize)]
pub struct VersionResponse {
#[serde(rename = "repoid")]
pub repo_id: String,
pub version: String,
pub release: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum NodeStatus {
Unknown,
Online,
Offline,
}
#[derive(Debug, Deserialize)]
pub struct NodeResponse {
#[serde(rename = "node")]
pub name: String,
pub status: NodeStatus,
pub cpu: Option<f32>,
pub support: Option<String>,
#[serde(rename = "maxcpu")]
pub max_cpu: Option<u16>,
#[serde(rename = "maxmem")]
pub max_mem: Option<u64>,
pub mem: Option<u64>,
pub uptime: Option<u64>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum VmStatus {
Stopped,
Running,
}
#[derive(Debug, Deserialize)]
pub struct VmInfo {
pub status: VmStatus,
#[serde(rename = "vmid")]
pub vm_id: i32,
}

View File

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