Files
ProxmoxAAS-API/src/backends/pve.js
T

270 lines
8.0 KiB
JavaScript

import axios from "axios";
import { PVE_BACKEND } from "./backends.js";
export default class PVE extends PVE_BACKEND {
#pveAPIURL = null;
#pveAPIToken = null;
#pveRoot = null;
#paasFabric = null;
constructor (config) {
super();
this.#pveAPIURL = config.url;
this.#pveAPIToken = config.token;
this.#pveRoot = config.root;
this.#paasFabric = config.fabric;
}
async openSession (user, password) {
const credentials = { username: `${user.id}@${user.realm}`, password };
const response = await global.pve.requestPVE("/access/ticket", "POST", null, credentials);
if (!(response.status === 200)) {
return {
ok: false,
status: response.status,
message: "Authorization failed",
cookies: []
};
}
const ticket = response.data.ticket;
const csrftoken = response.data.CSRFPreventionToken;
return {
ok: true,
status: response.status,
cookies: [
{
name: "PVEAuthCookie",
value: ticket,
expiresMSFromNow: 2 * 60 * 60 * 1000
},
{
name: "CSRFPreventionToken",
value: csrftoken,
expiresMSFromNow: 2 * 60 * 60 * 1000
}
]
};
}
/**
* Send HTTP request to proxmox API. Allows requests to be made with user cookie credentials or an API token for controlled priviledge elevation.
* @param {string} path HTTP path, prepended with the proxmox API base url.
* @param {string} method HTTP method.
* @param {Object} auth authentication method. Set auth.cookies with user cookies or auth.token with PVE API Token. Optional.
* @param {string} body body parameters and data to be sent. Optional.
* @returns {Object} HTTP response object or HTTP error object.
*/
async requestPVE (path, method, auth = null, body = null) {
const url = `${this.#pveAPIURL}${path}`;
const content = {
method,
mode: "cors",
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: body
};
if (auth && auth.cookies) { // user cookie credentials
content.headers.CSRFPreventionToken = auth.cookies.CSRFPreventionToken;
content.headers.Cookie = `PVEAuthCookie=${auth.cookies.PVEAuthCookie}; CSRFPreventionToken=${auth.cookies.CSRFPreventionToken}`;
}
else if (auth && auth.token) { // upgraded request as api
const token = this.#pveAPIToken;
content.headers.Authorization = `PVEAPIToken=${token.user}@${token.realm}!${token.id}=${token.uuid}`;
}
else if (auth && auth.root) { // upgraded request as root
const rootauth = await this.requestPVE("/access/ticket", "POST", null, this.#pveRoot);
if (!(rootauth.status === 200)) {
return rootauth.response;
}
const rootcookie = rootauth.data.ticket;
const rootcsrf = rootauth.data.CSRFPreventionToken;
content.headers.CSRFPreventionToken = rootcsrf;
content.headers.Cookie = `PVEAuthCookie=${rootcookie}; CSRFPreventionToken=${rootcsrf}`;
}
try {
const result = await axios.request(url, content);
return {
ok: result.ok,
status: result.status,
data: result.data.data, // pve returns {data: {data: {...}}}, unwrap here to conform to standard {data: {...}} format
headers: result.headers
};
}
catch (error) {
console.log(`pve: error ocuured in pve.requestPVE: ${method} ${path} resulted in ${error}`);
const result = error.response;
result.ok = result.status === 200;
return result;
}
}
/**
* Send HTTP request to PAAS Fabric
* @param {string} path HTTP path, prepended with the proxmox API base url.
* @param {string} method HTTP method.
* @param {Object} auth authentication method. Set auth.cookies with user cookies or auth.token with PVE API Token. Optional.
* @param {string} body body parameters and data to be sent. Optional.
* @returns {Object} HTTP response object or HTTP error object.
*/
async requestFabric (path, method, body = null) {
const url = `${this.#paasFabric}${path}`;
const content = {
method,
mode: "cors",
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: body
};
try {
return await axios.request(url, content);
}
catch (error) {
console.log(`pve: error ocuured in pve.requestFabric: ${method} ${path} resulted in ${error}`);
return error;
}
}
/**
* Handle various proxmox API responses. Handles sync and async responses.
* In sync responses, responses are completed when the response arrives. Method returns the response directly.
* In async responses, proxmox sends responses with a UPID to track process completion. Method returns the status of the proxmox process once it completes.
* @param {string} node response originates from.
* @param {Object} result response from proxmox.
* @param {Object} res response object of ProxmoxAAS API call.
*/
async handleResponse (node, result, res) {
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
if (result.status !== 200) {
res.status(result.status).send({ error: result.statusText });
res.end();
}
else if (result.data && typeof (result.data) === "string" && result.data.startsWith("UPID:")) {
const upid = result.data;
let taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
while (taskStatus.data.status !== "stopped") {
await waitFor(100);
taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
}
if (taskStatus.data.exitstatus === "OK") {
const result = taskStatus.data;
const taskLog = await this.requestPVE(`/nodes/${node}/tasks/${upid}/log`, "GET", { token: true });
result.log = taskLog.data;
res.status(200).send(result);
res.end();
}
else {
const result = taskStatus.data;
const taskLog = await this.requestPVE(`/nodes/${node}/tasks/${upid}/log`, "GET", { token: true });
result.log = taskLog.data;
res.status(500).send(result);
res.end();
}
}
else {
res.status(result.status).send(result.data);
res.end();
}
}
async getNode (node) {
const res = await this.requestFabric(`/nodes/${node}`, "GET");
if (res.status !== 200) {
console.error(res);
return null;
}
return res.data.node;
}
async syncNode (node) {
this.requestFabric(`/nodes/${node}/sync`, "POST");
}
async getInstance (node, instance) {
const res = await this.requestFabric(`/nodes/${node}/instances/${instance}`, "GET");
if (res.status !== 200) {
console.error(res);
return null;
}
return res.data.instance;
}
async syncInstance (node, vmid) {
this.requestFabric(`/nodes/${node}/instances/${vmid}/sync`, "POST");
}
async getDisk (node, instance, disk) {
const config = await this.getInstance(node, instance);
if (config !== null && config.volumes[disk] !== null) {
return config.volumes[disk];
}
else {
return null;
}
}
async getNet (node, instance, netid) {
const config = await this.getInstance(node, instance);
if (config !== null && config.nets[netid] !== null) {
return config.nets[netid];
}
else {
return null;
}
}
async getDevice (node, instance, deviceid) {
const config = await this.getInstance(node, instance);
if (config !== null && config.devices[deviceid] !== null) {
return config.devices[deviceid];
}
else {
return null;
}
}
async getPoolResources (cookies, pool) {
// get pool resources
const res = await this.requestPVE(`/pools/?poolid=${pool}`, "GET", { cookies });
if (res.status !== 200) {
return null;
}
const data = res.data;
if (data.length !== 1) {
return null;
}
const poolPVE = data[0];
if (poolPVE.poolid !== pool) {
return null;
}
const poolPVEResources = poolPVE.members;
const resources = {};
// for each resource, add to the object
for (const resource of poolPVEResources) {
// only add type if it is vm or ct (ie has vmid)
if (resource.vmid) {
const instance = await this.getInstance(resource.node, resource.vmid);
if (instance === null) {
return null;
}
else {
instance.node = resource.node;
resources[resource.vmid] = instance;
}
}
}
return resources;
}
}