Add log API

This commit is contained in:
Thomas E Lackey 2024-02-15 13:55:48 -06:00
parent 1168a00d33
commit 5160a3f692
4 changed files with 95 additions and 50 deletions

View File

@ -15,10 +15,8 @@ describe('greeter function', () => {
});
// Assert if setTimeout was called properly
it('delays the greeting by 2 seconds', () => {
});
it('delays the greeting by 2 seconds', () => {});
// Assert greeter result
it('greets a user with `Hello, {name}` message', () => {
});
it('greets a user with `Hello, {name}` message', () => {});
});

View File

@ -1,11 +1,11 @@
import yaml from 'js-yaml'
import fs from 'fs'
import path from 'path'
import {Registry} from "@cerc-io/laconic-sdk";
import yaml from 'js-yaml';
import fs from 'fs';
import path from 'path';
import {Registry} from '@cerc-io/laconic-sdk';
const loadConfigFile = (configFilePath: string): any => {
const resolvedFilePath = path.resolve(process.cwd(), configFilePath);
const configFile = fs.readFileSync(resolvedFilePath, 'utf-8')
const configFile = fs.readFileSync(resolvedFilePath, 'utf-8');
return yaml.load(configFile);
};
@ -13,14 +13,19 @@ export const Config = {
LISTEN_PORT: parseInt(process.env.LISTEN_PORT || '9555'),
LISTEN_ADDR: process.env.LISTEN_ADDR || '0.0.0.0',
LACONIC_CONFIG: process.env.LACONIC_CONFIG || '/etc/config/laconic.yml',
DEPLOYER_STATE: process.env.DEPLOYER_STATE || '/srv/deployments/autodeploy.state'
DEPLOYER_STATE:
process.env.DEPLOYER_STATE || '/srv/deployments/autodeploy.state',
UNDEPLOYER_STATE:
process.env.UNDEPLOYER_STATE || '/srv/deployments/autoundeploy.state',
BUILD_LOGS: process.env.BUILD_LOGS || '/srv/logs',
};
export const getRegistry = (): Registry => {
const laconicConfig = loadConfigFile(Config.LACONIC_CONFIG);
//TODO(telackey): Use a pool.
return new Registry(laconicConfig.services?.cns?.gqlEndpoint,
return new Registry(
laconicConfig.services?.cns?.gqlEndpoint,
laconicConfig.services?.cns?.restEndpoint,
laconicConfig.services?.cns?.chainId);
}
laconicConfig.services?.cns?.chainId,
);
};

View File

@ -1,15 +1,14 @@
import {getRegistry} from "./config.js";
import {Registry} from "@cerc-io/laconic-sdk";
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 lastPart = app.attributes.name.split('/').pop();
const hasher = createHash('sha256');
hasher.update(app.attributes.name);
hasher.update("|")
hasher.update('|');
if (Array.isArray(app.attributes.repository)) {
hasher.update(app.attributes.repository[0]);
} else {
@ -18,7 +17,7 @@ function generateHostnameForApp(app){
return `${lastPart}-${hasher.digest('hex').slice(0, 10)}`;
}
export type RequestState = "SUBMITTED" | "DEPLOYING" | "DEPLOYED" | "REMOVED" | "CANCELLED" | "ERROR";
export type RequestState = | 'SUBMITTED' | 'DEPLOYING' | 'DEPLOYED' | 'REMOVED' | 'CANCELLED' | 'ERROR';
export class RequestStatus {
public app?: any;
@ -26,8 +25,9 @@ export class RequestStatus {
public url?: string;
public deployment?: string;
public lastUpdate?: string;
constructor(public id: string, public createTime: string) {
this.lastState = "SUBMITTED";
constructor(public id: string, public createTime: string,) {
this.lastState = 'SUBMITTED';
this.lastUpdate = this.createTime;
}
}
@ -82,7 +82,7 @@ export class RegHelper {
if (!idOrName) {
return null;
}
if (idOrName.startsWith("crn:")) {
if (idOrName.startsWith('crn:')) {
return this.resolveName(idOrName);
}
return this.getRecordById(idOrName);
@ -105,11 +105,17 @@ export class RegHelper {
}
async deploymentRequestStatus() {
const requests = await this.queryRecords({type: "ApplicationDeploymentRequest"});
const deployments = await this.queryRecords({type: "ApplicationDeploymentRecord"});
const removalRequests = await this.queryRecords({type: "ApplicationDeploymentRemovalRequest"});
const requests = await this.queryRecords({
type: 'ApplicationDeploymentRequest',
});
const deployments = await this.queryRecords({
type: 'ApplicationDeploymentRecord',
});
const removalRequests = await this.queryRecords({
type: 'ApplicationDeploymentRemovalRequest',
});
requests.sort((a, b) => a.createTime === b.createTime ? 0 : a.createTime > b.createTime ? 1 : -1);
requests.sort((a, b) => a.createTime === b.createTime ? 0 : a.createTime > b.createTime ? 1 : -1,);
requests.reverse();
const deploymentsByRequest = new Map<string, any>();
@ -123,7 +129,6 @@ export class RegHelper {
}
}
const latestByHostname = new Map<string, any>();
const ret = [];
@ -137,33 +142,33 @@ export class RegHelper {
const deployment = deploymentsByRequest.get(r.id);
status.url = deployment.attributes.url;
status.lastUpdate = deployment.createTime;
const shortHost = new URL(status.url).host.split(".").shift();
const shortHost = new URL(status.url).host.split('.').shift();
if (!latestByHostname.has(shortHost)) {
latestByHostname.set(shortHost, status);
}
status.deployment = deployment.names ? deployment.names[0] : null;
if (status.deployment) {
status.lastState = "DEPLOYED";
status.lastState = 'DEPLOYED';
} else {
status.lastState = "REMOVED";
status.lastState = 'REMOVED';
}
continue;
}
if (removalsByRequest.has(r.id)) {
status.lastState = "CANCELLED";
status.lastState = 'CANCELLED';
continue;
}
const app = await this.getRecord(r.attributes.application);
if (!app) {
status.lastState = "ERROR";
status.lastState = 'ERROR';
continue;
}
const shortHost = r.attributes.dns ?? generateHostnameForApp(app);
if (latestByHostname.has(shortHost)) {
status.lastState = "CANCELLED";
status.lastState = 'CANCELLED';
continue;
}
@ -174,6 +179,6 @@ export class RegHelper {
}
async deployments() {
return this.queryRecords({type: "ApplicationDeploymentRecord"}, false);
return this.queryRecords({type: 'ApplicationDeploymentRecord'}, false);
}
}

View File

@ -1,28 +1,55 @@
import express from 'express';
import { readFileSync, existsSync } from "fs";
import {existsSync, readdirSync, readFileSync} from 'fs';
import {Config, getRegistry} from './config.js'
import {Config, getRegistry} from './config.js';
import {RegHelper} from './deployments.js'
import {RegHelper} from './deployments.js';
const app = express();
app.use(express.json());
app.use(function (_req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept',);
next();
});
const logForRequest = (req_id) => {
let ret = null;
const logDir = `${Config.BUILD_LOGS}/${req_id}`;
if (existsSync(logDir)) {
const logFiles = readdirSync(logDir).sort();
if (logFiles.length) {
ret = `${logDir}/${logFiles.pop()}`;
}
}
return ret;
};
app.get('/', async (_req, res) => {
const reg = new RegHelper();
const regStatus = await reg.deploymentRequestStatus();
let deployerState = {};
if (Config.DEPLOYER_STATE && existsSync(Config.DEPLOYER_STATE)) {
const deployerState = JSON.parse(readFileSync(Config.DEPLOYER_STATE).toString());
deployerState = JSON.parse(readFileSync(Config.DEPLOYER_STATE).toString());
}
for (const r of regStatus) {
if (deployerState[r.id] && deployerState[r.id].status && deployerState[r.id].status != "SEEN") {
console.log(deployerState[r.id])
r.logAvailable = null != logForRequest(r.id);
// If we know something more specific in the local state, use that ...
if (deployerState[r.id]) {
switch (deployerState[r.id].state) {
case 'DEPLOYED': {
r.lastState = deployerState[r.id].status;
break;
}
case 'DEPLOYING': {
r.lastState = deployerState[r.id].status;
break;
}
case 'ERROR': {
r.lastState = deployerState[r.id].status;
break;
}
}
}
}
@ -35,6 +62,16 @@ app.get('/:id', async (req, res) => {
res.send(records);
});
app.get('/log/:id', async (req, res) => {
const logFile = logForRequest(req.params.id);
if (!logFile) {
res.sendStatus(404);
return;
}
res.send(readFileSync(logFile));
});
app.listen(Config.LISTEN_PORT, Config.LISTEN_ADDR, () => {
console.log(`listening on ${Config.LISTEN_ADDR}:${Config.LISTEN_PORT}`);
});