Files
pulse/server/apiClients.js
T

214 lines
8.0 KiB
JavaScript

const axios = require('axios');
const https = require('https');
const axiosRetry = require('axios-retry').default;
/**
* Creates a request interceptor for PVE API authentication.
* @param {Object} endpoint - The PVE endpoint configuration.
* @returns {Function} - An Axios request interceptor function.
*/
function createPveAuthInterceptor(endpoint) {
return config => {
if (endpoint.tokenId && endpoint.tokenSecret) {
config.headers.Authorization = `PVEAPIToken=${endpoint.tokenId}=${endpoint.tokenSecret}`;
} else {
// Error condition for missing credentials
console.error(`ERROR: Endpoint ${endpoint.name} is missing required API token credentials.`);
}
return config;
};
}
/**
* Logs a warning and calculates exponential delay for PVE retries.
* @param {string} endpointName - The name of the PVE endpoint.
* @param {number} retryCount - The current retry attempt number.
* @param {Error} error - The error that caused the retry.
* @returns {number} - The delay in milliseconds.
*/
function pveRetryDelayLogger(endpointName, retryCount, error) {
console.warn(`Retrying PVE API request for ${endpointName} (attempt ${retryCount}) due to error: ${error.message}`);
return axiosRetry.exponentialDelay(retryCount);
}
/**
* Checks if an error warrants retrying a PVE API call.
* @param {Error} error - The error object.
* @returns {boolean} - True if the request should be retried, false otherwise.
*/
function pveRetryConditionChecker(error) {
return (
axiosRetry.isNetworkError(error) ||
axiosRetry.isRetryableError(error) ||
error.response?.status === 596 // Specific PVE status code
);
}
/**
* Initializes Axios clients for Proxmox VE endpoints.
* @param {Array} endpoints - Array of PVE endpoint configuration objects.
* @returns {Object} - Object containing initialized PVE API clients keyed by endpoint ID.
*/
function initializePveClients(endpoints) {
const apiClients = {};
console.log(`INFO: Initializing API clients for ${endpoints.length} Proxmox VE endpoints...`);
endpoints.forEach(endpoint => {
if (!endpoint.enabled) {
console.log(`INFO: Skipping disabled PVE endpoint: ${endpoint.name} (${endpoint.host})`);
return; // Skip disabled endpoints
}
const baseURL = endpoint.host.includes('://')
? `${endpoint.host}/api2/json`
: `https://${endpoint.host}:${endpoint.port}/api2/json`;
const authInterceptor = createPveAuthInterceptor(endpoint);
const retryConfig = {
retryDelayLogger: pveRetryDelayLogger.bind(null, endpoint.name),
retryConditionChecker: pveRetryConditionChecker,
};
const apiClient = createApiClientInstance(baseURL, endpoint.allowSelfSignedCerts, authInterceptor, retryConfig);
apiClients[endpoint.id] = { client: apiClient, config: endpoint };
console.log(`INFO: Initialized PVE API client for endpoint: ${endpoint.name} (${endpoint.host})`);
});
return apiClients;
}
// Generic function to create an Axios API client instance
function createApiClientInstance(baseURL, allowSelfSignedCerts, authInterceptor, retryConfig) {
const apiClient = axios.create({
baseURL: baseURL,
timeout: 30000, // 30 second timeout
httpsAgent: new https.Agent({
rejectUnauthorized: !allowSelfSignedCerts
}),
headers: {
'Content-Type': 'application/json'
}
});
if (authInterceptor) {
apiClient.interceptors.request.use(authInterceptor);
}
if (retryConfig) {
axiosRetry(apiClient, {
retries: retryConfig.retries || 3,
retryDelay: retryConfig.retryDelayLogger,
retryCondition: retryConfig.retryConditionChecker,
});
}
return apiClient;
}
/**
* Logs a warning and calculates exponential delay for PBS retries.
* @param {string} configName - The name of the PBS configuration.
* @param {number} retryCount - The current retry attempt number.
* @param {Error} error - The error that caused the retry.
* @returns {number} - The delay in milliseconds.
*/
function pbsRetryDelayLogger(configName, retryCount, error) {
console.warn(`Retrying PBS API request for ${configName} (Token Auth - attempt ${retryCount}) due to error: ${error.message}`);
return axiosRetry.exponentialDelay(retryCount);
}
/**
* Checks if an error warrants retrying a PBS API call.
* @param {Error} error - The error object.
* @returns {boolean} - True if the request should be retried, false otherwise.
*/
function pbsRetryConditionChecker(error) {
return axiosRetry.isNetworkError(error) || axiosRetry.isRetryableError(error);
}
/**
* Creates a request interceptor for PBS API authentication (Token Auth).
* @param {Object} config - The PBS configuration object.
* @returns {Function} - An Axios request interceptor function.
*/
function createPbsAuthInterceptor(config) {
return reqConfig => {
// Assumes config.tokenId and config.tokenSecret exist (checked during config load perhaps?)
reqConfig.headers.Authorization = `PBSAPIToken=${config.tokenId}:${config.tokenSecret}`;
return reqConfig;
};
}
/**
* Initializes Axios clients for Proxmox Backup Server instances.
* @param {Array} pbsConfigs - Array of PBS configuration objects.
* @returns {Promise<Object>} - Promise resolving to an object containing initialized PBS API clients keyed by config ID.
*/
async function initializePbsClients(pbsConfigs) {
const pbsApiClients = {};
if (pbsConfigs.length === 0) {
console.log("INFO: No PBS instances configured, skipping PBS client initialization.");
return pbsApiClients;
}
console.log(`INFO: Initializing API clients for ${pbsConfigs.length} PBS instances...`);
const initPromises = pbsConfigs.map(async (config) => {
let clientData = null;
try {
if (config.authMethod === 'token') {
const pbsBaseURL = config.host.includes('://')
? `${config.host}/api2/json`
: `https://${config.host}:${config.port}/api2/json`;
const authInterceptor = createPbsAuthInterceptor(config);
const retryConfig = {
retryDelayLogger: pbsRetryDelayLogger.bind(null, config.name),
retryConditionChecker: pbsRetryConditionChecker,
};
const pbsAxiosInstance = createApiClientInstance(pbsBaseURL, config.allowSelfSignedCerts, authInterceptor, retryConfig);
clientData = { client: pbsAxiosInstance, config: config };
console.log(`INFO: [PBS Init] Successfully initialized client for instance '${config.name}' (Token Auth)`);
} else {
console.error(`ERROR: Unexpected authMethod '${config.authMethod}' found during PBS client initialization for: ${config.name}`);
}
if (clientData) {
pbsApiClients[config.id] = clientData;
}
} catch (error) {
console.error(`ERROR: Unhandled exception during PBS client initialization for ${config.name}: ${error.message}`, error.stack);
}
// We don't return clientData here, we modify pbsApiClients directly
});
await Promise.allSettled(initPromises);
console.log(`INFO: [PBS Init] Finished initialization. ${Object.keys(pbsApiClients).length} / ${pbsConfigs.length} PBS clients initialized successfully.`);
return pbsApiClients;
}
/**
* Initializes all Proxmox VE and PBS API clients.
* @param {Array} endpoints - Array of PVE endpoint configuration objects.
* @param {Array} pbsConfigs - Array of PBS configuration objects.
* @returns {Promise<Object>} - Promise resolving to an object containing { apiClients, pbsApiClients }.
*/
async function initializeApiClients(endpoints, pbsConfigs) {
const apiClients = initializePveClients(endpoints);
const pbsApiClients = await initializePbsClients(pbsConfigs); // Wait for PBS clients to initialize
return { apiClients, pbsApiClients };
}
// Export the new helper functions for potential direct testing
module.exports = {
initializeApiClients,
createPveAuthInterceptor,
createPbsAuthInterceptor,
pveRetryDelayLogger,
pveRetryConditionChecker,
pbsRetryDelayLogger,
pbsRetryConditionChecker,
createApiClientInstance,
};