import {getRegistry} from './config.js'; import {Registry} from '@cerc-io/laconic-sdk'; import stringify from 'json-stable-stringify'; import {createHash} from 'crypto'; function generateHostnameForApp(app) { const lastPart = app.attributes.name.split('/').pop(); const hasher = createHash('sha256'); hasher.update(app.attributes.name); hasher.update('|'); if (Array.isArray(app.attributes.repository)) { hasher.update(app.attributes.repository[0]); } else { hasher.update(app.attributes.repository); } return `${lastPart}-${hasher.digest('hex').slice(0, 10)}`; } export type RequestState = | 'SUBMITTED' | 'DEPLOYING' | 'DEPLOYED' | 'REMOVED' | 'CANCELLED' | 'ERROR'; export class RequestStatus { public app?: any; public lastState?: RequestState; public url?: string; public deployment?: string; public lastUpdate?: string; constructor(public id: string, public createTime: string,) { this.lastState = 'SUBMITTED'; this.lastUpdate = this.createTime; } } export class RegHelper { private registry: Registry; private cache = new Map(); constructor() { this.registry = getRegistry(); } private cacheKey(keyObj: any): any | null { return stringify(keyObj); } async resolveName(name: string) { if (this.cache.has(name)) { return this.cache.get(name); } else { const records = await this.registry.resolveNames([name]); for (const r of records) { // r can be null if (r) { this.cache.set(r.id, r); if (Array.isArray(r.names)) { for (const n of r.names) { this.cache.set(n, r); } } } } return records[0]; } } async getRecordById(id: string) { if (this.cache.has(id)) { return this.cache.get(id); } else { const records = await this.registry.getRecordsByIds([id]); for (const r of records) { this.cache.set(r.id, r); if (Array.isArray(r.names)) { for (const n of r.names) { this.cache.set(n, r); } } } return records[0]; } } async getRecord(idOrName: string) { if (!idOrName) { return null; } if (idOrName.startsWith('lrn:')) { return this.resolveName(idOrName); } return this.getRecordById(idOrName); } async queryRecords(query, all = true) { const cacheKey = this.cacheKey({query, all}); let records; if (this.cache.has(cacheKey)) { records = this.cache.get(cacheKey); } else { records = await this.registry.queryRecords(query, all); this.cache.set(cacheKey, records); for (const r of records) { this.cache.set(r.id, r); } } return [...records]; } async deploymentRequestStatus(requestId?: string) { const requests: any[] = []; const deployments: any[] = []; const removalRequests = await this.queryRecords({ type: 'ApplicationDeploymentRemovalRequest', }); if (requestId) { const request = await this.getRecordById(requestId); if (request) { requests.push(request); } deployments.push(...await this.queryRecords({ type: 'ApplicationDeploymentRecord', request: requestId })); } else { requests.push(...await this.queryRecords({ type: 'ApplicationDeploymentRequest', })); deployments.push(...await this.queryRecords({ type: 'ApplicationDeploymentRecord', })); } requests.sort((a, b) => a.createTime === b.createTime ? 0 : a.createTime > b.createTime ? 1 : -1,); requests.reverse(); const deploymentsByRequest = new Map(); for (const d of deployments) { deploymentsByRequest.set(d.attributes.request, d); } const removalsByRequest = new Map(); for (const rr of removalRequests) { if (rr.attributes.request) { removalsByRequest.set(rr.attributes.request, rr); } } const latestByHostname = new Map(); const ret = []; for (const r of requests) { const status = new RequestStatus(r.id, r.createTime); ret.push(status); const app = await this.getRecord(r.attributes.application); if (!app) { status.lastState = 'ERROR'; continue; } status.app = r.attributes.application; const dnsList: string[] = r.attributes.dns ? r.attributes.dns.split(",") : []; const hostname = dnsList.length ? dnsList[dnsList.length - 1] : generateHostnameForApp(app); if (deploymentsByRequest.has(r.id)) { const deployment = deploymentsByRequest.get(r.id); status.url = deployment.attributes.url; status.lastUpdate = deployment.createTime; if (!latestByHostname.has(hostname)) { latestByHostname.set(hostname, status); } status.deployment = deployment.names ? deployment.names[0] : null; if (status.deployment) { status.lastState = 'DEPLOYED'; } else { status.lastState = 'REMOVED'; } continue; } if (removalsByRequest.has(r.id)) { status.lastState = 'CANCELLED'; continue; } if (latestByHostname.has(hostname)) { status.lastState = 'CANCELLED'; continue; } latestByHostname.set(hostname, status); } return ret; } async deployments() { return this.queryRecords({type: 'ApplicationDeploymentRecord'}, false); } }