/** * Auto-Restart Policy Routes * * CRUD endpoints for per-container auto-restart policies. * Also provides a dry-run test endpoint. * * @module routes/auto-restart */ const express = require('express'); const { success } = require('../src/utils/responses'); const { ValidationError, NotFoundError } = require('../src/utilities/errors'); /** * Auto-restart route factory * * @param {Object} deps - Explicit dependencies * @param {Object} deps.autoRestartManager - AutoRestartManager instance * @param {Function} deps.asyncHandler - Async route handler wrapper * @param {Function} deps.logError - Error logging function * @returns {express.Router} */ module.exports = function ({ autoRestartManager, asyncHandler, logError }) { const router = express.Router(); /** * GET /auto-restart/policies * List all configured auto-restart policies. */ router.get('/policies', asyncHandler(async (_req, res) => { const policies = autoRestartManager.listPolicies(); success(res, { policies }); }, 'auto-restart-list')); /** * GET /auto-restart/policies/:serviceId * Get the restart policy for a single service. */ router.get('/policies/:serviceId', asyncHandler(async (req, res) => { const { serviceId } = req.params; if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) { throw new ValidationError('Invalid service ID format'); } const policy = autoRestartManager.getPolicy(serviceId); if (!policy) { throw new NotFoundError(`Auto-restart policy for "${serviceId}"`); } success(res, { policy }); }, 'auto-restart-get')); /** * POST /auto-restart/policies/:serviceId * Create or update a restart policy. * * Body: { enabled, maxRetries, retryIntervalMs, windowMinutes } */ router.post('/policies/:serviceId', asyncHandler(async (req, res) => { const { serviceId } = req.params; if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) { throw new ValidationError('Invalid service ID format'); } const { enabled, maxRetries, retryIntervalMs, windowMinutes } = req.body; // Validate inputs if (enabled !== undefined && typeof enabled !== 'boolean') { throw new ValidationError('enabled must be a boolean'); } if (maxRetries !== undefined) { if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 100) { throw new ValidationError('maxRetries must be an integer between 0 and 100'); } } if (retryIntervalMs !== undefined) { if (!Number.isInteger(retryIntervalMs) || retryIntervalMs < 0 || retryIntervalMs > 3600000) { throw new ValidationError('retryIntervalMs must be an integer between 0 and 3600000'); } } if (windowMinutes !== undefined) { if (!Number.isInteger(windowMinutes) || windowMinutes < 0 || windowMinutes > 1440) { throw new ValidationError('windowMinutes must be an integer between 0 and 1440'); } } const policy = await autoRestartManager.setPolicy(serviceId, { ...(enabled !== undefined && { enabled }), ...(maxRetries !== undefined && { maxRetries }), ...(retryIntervalMs !== undefined && { retryIntervalMs }), ...(windowMinutes !== undefined && { windowMinutes }), }); success(res, { policy, message: `Policy ${serviceId} saved` }); }, 'auto-restart-set')); /** * DELETE /auto-restart/policies/:serviceId * Remove a restart policy. */ router.delete('/policies/:serviceId', asyncHandler(async (req, res) => { const { serviceId } = req.params; if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) { throw new ValidationError('Invalid service ID format'); } const removed = await autoRestartManager.removePolicy(serviceId); if (!removed) { throw new NotFoundError(`Auto-restart policy for "${serviceId}"`); } success(res, { message: `Policy for "${serviceId}" removed` }); }, 'auto-restart-delete')); /** * POST /auto-restart/policies/:serviceId/test * Dry-run: simulate a restart attempt without actually restarting. * Returns what *would* happen given the current policy state. */ router.post('/policies/:serviceId/test', asyncHandler(async (req, res) => { const { serviceId } = req.params; if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) { throw new ValidationError('Invalid service ID format'); } const policy = autoRestartManager.getPolicy(serviceId); if (!policy) { throw new NotFoundError(`Auto-restart policy for "${serviceId}"`); } const now = Date.now(); const inCooldown = policy.cooldownUntil && now < policy.cooldownUntil; const wouldRetry = !inCooldown && policy.currentRetries < policy.maxRetries; const nextAttempt = policy.currentRetries + 1; success(res, { dryRun: true, serviceId, policy: { enabled: policy.enabled, currentRetries: policy.currentRetries, maxRetries: policy.maxRetries, cooldownUntil: policy.cooldownUntil, inCooldown, }, wouldRestart: policy.enabled && wouldRetry, wouldMaxOut: !wouldRetry && !inCooldown, nextAttempt: wouldRetry ? nextAttempt : null, message: !policy.enabled ? 'Policy is disabled — no restart would occur' : inCooldown ? `In cooldown until ${new Date(policy.cooldownUntil).toISOString()} — would skip` : wouldRetry ? `Would attempt restart ${nextAttempt}/${policy.maxRetries}` : `Max retries (${policy.maxRetries}) already reached — would enter cooldown`, }); }, 'auto-restart-test')); return router; };