Add configurable retention for successful routine operations
This commit is contained in:
@@ -4,6 +4,8 @@ import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypt
|
||||
import { isIP } from 'node:net';
|
||||
import { db } from '../db/index.js';
|
||||
import { ensureHostSchema } from './schema.js';
|
||||
import { successfulObservation } from './observations.js';
|
||||
import { fleetSettings } from '../settings.js';
|
||||
import { recordInstanceResources, validateInstanceResources } from './calibration.js';
|
||||
import { refreshHostAvailability, scheduleAvailabilityRefresh } from './availability.js';
|
||||
import type { Host, HostDecommission, HostDeletion, HostInventory, HostMode, HostOperation, HostOperationHistoryPage, HostOperationState } from '../../hosts.js';
|
||||
@@ -372,6 +374,24 @@ export function listHostOperationHistory(page = 1): HostOperationHistoryPage {
|
||||
return { operations, page: currentPage, pageSize, total, totalPages };
|
||||
})();
|
||||
}
|
||||
|
||||
/** Bound each cleanup pass so retained history cannot stall monitoring or receipts. */
|
||||
export function pruneHostObservations(): number {
|
||||
const days = fleetSettings().observationKeepDays;
|
||||
if (!Number.isSafeInteger(days) || days <= 0 || days > 36500) return 0;
|
||||
const handle = database();
|
||||
const cutoff = new Date(Date.now() - days * 86_400_000).toISOString();
|
||||
return handle.transaction(() => {
|
||||
const rows = handle.prepare(`SELECT id FROM ec_host_operations WHERE observation_success=1 AND state='completed'
|
||||
AND updated_at<? ORDER BY updated_at LIMIT 500`).all(cutoff) as { id: string }[];
|
||||
if (!rows.length) return 0;
|
||||
const ids = rows.map(row => row.id);
|
||||
const placeholders = ids.map(() => '?').join(',');
|
||||
handle.prepare(`DELETE FROM ec_host_operation_events WHERE operation_id IN (${placeholders})`).run(...ids);
|
||||
handle.prepare(`DELETE FROM ec_host_operation_payloads WHERE operation_id IN (${placeholders})`).run(...ids);
|
||||
return handle.prepare(`DELETE FROM ec_host_operations WHERE id IN (${placeholders})`).run(...ids).changes;
|
||||
})();
|
||||
}
|
||||
/** Heartbeats can tell an executing create to stop at its next safe command boundary. */
|
||||
export function pendingCreationCancellations(hostId: string): string[] {
|
||||
return (database().prepare(`SELECT json_extract(payload,'$.createOperationId') AS id FROM ec_host_operations
|
||||
@@ -479,7 +499,10 @@ export function reportHostOperationStatus(hostId: string, id: string, input: { s
|
||||
const error = input.error === undefined || input.error === null ? null : String(input.error).slice(0, 4000);
|
||||
const result = input.result === undefined ? null : JSON.stringify(input.result);
|
||||
if (result !== null && Buffer.byteLength(result) > 128 * 1024 * 1024) throw new Error('Operation result exceeds the limit.');
|
||||
database().prepare('UPDATE ec_host_operations SET state=?,result=?,error=?,updated_at=? WHERE id=?').run(state, result, error, now(), id);
|
||||
const observation = state === 'completed' && !error && successfulObservation(operation.kind,
|
||||
operation.kind === 'rcon.execute' ? JSON.parse(getHostOperationPayload(hostId, id).toString('utf8')) : {}, input.result);
|
||||
database().prepare('UPDATE ec_host_operations SET state=?,result=?,error=?,updated_at=?,observation_success=? WHERE id=?')
|
||||
.run(state, result, error, now(), Number(observation), id);
|
||||
if (operation.kind === 'host.decommission') {
|
||||
if (state === 'failed') database().prepare("UPDATE ec_host_decommissions SET stage='failed',error=?,updated_at=? WHERE operation_id=?").run(error ?? 'The agent could not start cleanup. Repair the installation, then retry deletion.', now(), id);
|
||||
else database().prepare("UPDATE ec_host_decommissions SET stage='running',updated_at=? WHERE operation_id=? AND stage IN ('queued','running')").run(now(), id);
|
||||
|
||||
Reference in New Issue
Block a user