Add log API
This commit is contained in:
parent
1168a00d33
commit
5160a3f692
@ -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', () => {});
|
||||
});
|
||||
|
@ -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,
|
||||
);
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
53
src/main.ts
53
src/main.ts
@ -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}`);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user