Add P2 SaaS CI, P4 scheduled discovery, P6 agent bridge (Redis pub/sub), Caddyfile
- P2: Gitea Actions CI for SaaS backend (separate from Go agent CI) - P4: ScheduledDiscovery with Redis distributed lock to prevent concurrent scans - P6: AgentBridge — Redis pub/sub for SaaS↔agent communication (approvals + step results) - Caddyfile: self-hosted reverse proxy with auto-TLS for all 6 products
This commit is contained in:
64
products/04-lightweight-idp/src/discovery/scheduler.ts
Normal file
64
products/04-lightweight-idp/src/discovery/scheduler.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import pino from 'pino';
|
||||
import Redis from 'ioredis';
|
||||
import { config } from '../config/index.js';
|
||||
|
||||
const logger = pino({ name: 'scheduled-discovery' });
|
||||
|
||||
/**
|
||||
* Scheduled discovery job — runs AWS + GitHub scans on a cron schedule.
|
||||
* Uses Redis-based distributed lock to prevent concurrent scans.
|
||||
*/
|
||||
export class ScheduledDiscovery {
|
||||
private redis: Redis;
|
||||
private lockTtlMs: number;
|
||||
|
||||
constructor(redis: Redis, lockTtlMs = 10 * 60 * 1000) {
|
||||
this.redis = redis;
|
||||
this.lockTtlMs = lockTtlMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire a distributed lock for a tenant scan.
|
||||
* Returns true if lock acquired, false if another scan is running.
|
||||
*/
|
||||
async acquireLock(tenantId: string, scanner: string): Promise<boolean> {
|
||||
const key = `scan_lock:${tenantId}:${scanner}`;
|
||||
const result = await this.redis.set(key, Date.now().toString(), 'PX', this.lockTtlMs, 'NX');
|
||||
return result === 'OK';
|
||||
}
|
||||
|
||||
async releaseLock(tenantId: string, scanner: string): Promise<void> {
|
||||
const key = `scan_lock:${tenantId}:${scanner}`;
|
||||
await this.redis.del(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a scheduled scan for a tenant.
|
||||
* Called by cron job or manual trigger.
|
||||
*/
|
||||
async runScan(tenantId: string, scanner: 'aws' | 'github'): Promise<{ status: string; discovered: number }> {
|
||||
const locked = await this.acquireLock(tenantId, scanner);
|
||||
if (!locked) {
|
||||
logger.info({ tenantId, scanner }, 'Scan already in progress — skipping');
|
||||
return { status: 'skipped', discovered: 0 };
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info({ tenantId, scanner }, 'Starting scheduled scan');
|
||||
|
||||
// TODO: Instantiate appropriate scanner and run
|
||||
// const result = scanner === 'aws'
|
||||
// ? await awsScanner.scan(region, account)
|
||||
// : await githubScanner.scan(org);
|
||||
|
||||
// TODO: Merge results into catalog via CatalogService
|
||||
|
||||
return { status: 'completed', discovered: 0 };
|
||||
} catch (err) {
|
||||
logger.error({ tenantId, scanner, error: (err as Error).message }, 'Scheduled scan failed');
|
||||
return { status: 'failed', discovered: 0 };
|
||||
} finally {
|
||||
await this.releaseLock(tenantId, scanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user