fix: docker log sequence

This commit is contained in:
Andras Bacsai
2023-01-16 10:06:41 +01:00
parent d641d32413
commit 2fd001f6d2
5 changed files with 1617 additions and 1380 deletions

View File

@@ -1605,12 +1605,7 @@ export async function getApplicationLogs(request: FastifyRequest<GetApplicationL
.split('\n') .split('\n')
.map((l) => ansi(l)) .map((l) => ansi(l))
.filter((a) => a); .filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout); return { logs: stripLogsStderr.concat(stripLogsStdout) };
const sortedLogs = logs.sort((a, b) =>
day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1
);
return { logs: sortedLogs };
// }
} catch (error) { } catch (error) {
const { statusCode, stderr } = error; const { statusCode, stderr } = error;
if (stderr.startsWith('Error: No such container')) { if (stderr.startsWith('Error: No such container')) {

View File

@@ -3,11 +3,44 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeCommand, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; import {
ComposeFile,
createDirectories,
decrypt,
defaultComposeConfiguration,
encrypt,
errorHandler,
executeCommand,
generateDatabaseConfiguration,
generatePassword,
getContainerUsage,
getDatabaseImage,
getDatabaseVersions,
getFreePublicPort,
listSettings,
makeLabelForStandaloneDatabase,
prisma,
startTraefikTCPProxy,
stopDatabaseContainer,
stopTcpHttpProxy,
supportedDatabaseTypesAndVersions,
uniqueName,
updatePasswordInDb
} from '../../../../lib/common';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
import type { DeleteDatabase, DeleteDatabaseSecret, GetDatabaseLogs, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveDatabaseType, SaveVersion } from './types'; import type {
DeleteDatabase,
DeleteDatabaseSecret,
GetDatabaseLogs,
SaveDatabase,
SaveDatabaseDestination,
SaveDatabaseSecret,
SaveDatabaseSettings,
SaveDatabaseType,
SaveVersion
} from './types';
export async function listDatabases(request: FastifyRequest) { export async function listDatabases(request: FastifyRequest) {
try { try {
@@ -18,9 +51,9 @@ export async function listDatabases(request: FastifyRequest) {
}); });
return { return {
databases databases
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function newDatabase(request: FastifyRequest, reply: FastifyReply) { export async function newDatabase(request: FastifyRequest, reply: FastifyReply) {
@@ -46,33 +79,34 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
settings: { create: { isPublic: false } } settings: { create: { isPublic: false } }
} }
}); });
return reply.code(201).send({ id }) return reply.code(201).send({ id });
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function cleanupUnconfiguredDatabases(request: FastifyRequest) { export async function cleanupUnconfiguredDatabases(request: FastifyRequest) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
let databases = await prisma.database.findMany({ let databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true }, include: { settings: true, destinationDocker: true, teams: true }
}); });
for (const database of databases) { for (const database of databases) {
if (!database?.version) { if (!database?.version) {
const { id } = database; const { id } = database;
if (database.destinationDockerId) { if (database.destinationDockerId) {
const everStarted = await stopDatabaseContainer(database); const everStarted = await stopDatabaseContainer(database);
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); if (everStarted)
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
} }
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } }); await prisma.database.delete({ where: { id } });
} }
} }
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) { export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) {
@@ -89,7 +123,10 @@ export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) {
const { destinationDockerId, destinationDocker } = database; const { destinationDockerId, destinationDocker } = database;
if (destinationDockerId) { if (destinationDockerId) {
try { try {
const { stdout } = await executeCommand({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` }) const { stdout } = await executeCommand({
dockerId: destinationDocker.id,
command: `docker inspect --format '{{json .State}}' ${id}`
});
if (JSON.parse(stdout).Running) { if (JSON.parse(stdout).Running) {
isRunning = true; isRunning = true;
@@ -101,9 +138,9 @@ export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) {
} }
return { return {
isRunning isRunning
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
@@ -116,7 +153,7 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
include: { destinationDocker: true, settings: true } include: { destinationDocker: true, settings: true }
}); });
if (!database) { if (!database) {
throw { status: 404, message: 'Database not found.' } throw { status: 404, message: 'Database not found.' };
} }
const settings = await listSettings(); const settings = await listSettings();
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
@@ -129,19 +166,22 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
settings settings
}; };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getDatabaseTypes(request: FastifyRequest) { export async function getDatabaseTypes(request: FastifyRequest) {
try { try {
return { return {
types: supportedDatabaseTypesAndVersions types: supportedDatabaseTypesAndVersions
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveDatabaseType(request: FastifyRequest<SaveDatabaseType>, reply: FastifyReply) { export async function saveDatabaseType(
request: FastifyRequest<SaveDatabaseType>,
reply: FastifyReply
) {
try { try {
const { id } = request.params; const { id } = request.params;
const { type } = request.body; const { type } = request.body;
@@ -149,9 +189,9 @@ export async function saveDatabaseType(request: FastifyRequest<SaveDatabaseType>
where: { id }, where: { id },
data: { type } data: { type }
}); });
return reply.code(201).send({}) return reply.code(201).send({});
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getVersions(request: FastifyRequest<OnlyId>) { export async function getVersions(request: FastifyRequest<OnlyId>) {
@@ -166,9 +206,9 @@ export async function getVersions(request: FastifyRequest<OnlyId>) {
const versions = getDatabaseVersions(type, arch); const versions = getDatabaseVersions(type, arch);
return { return {
versions versions
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveVersion(request: FastifyRequest<SaveVersion>, reply: FastifyReply) { export async function saveVersion(request: FastifyRequest<SaveVersion>, reply: FastifyReply) {
@@ -179,15 +219,18 @@ export async function saveVersion(request: FastifyRequest<SaveVersion>, reply: F
await prisma.database.update({ await prisma.database.update({
where: { id }, where: { id },
data: { data: {
version, version
} }
}); });
return reply.code(201).send({}) return reply.code(201).send({});
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveDatabaseDestination(request: FastifyRequest<SaveDatabaseDestination>, reply: FastifyReply) { export async function saveDatabaseDestination(
request: FastifyRequest<SaveDatabaseDestination>,
reply: FastifyReply
) {
try { try {
const { id } = request.params; const { id } = request.params;
const { destinationId } = request.body; const { destinationId } = request.body;
@@ -208,12 +251,12 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
if (destinationDockerId) { if (destinationDockerId) {
if (type && version) { if (type && version) {
const baseImage = getDatabaseImage(type, arch); const baseImage = getDatabaseImage(type, arch);
executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` }) executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` });
} }
} }
return reply.code(201).send({}) return reply.code(201).send({});
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getDatabaseUsage(request: FastifyRequest<OnlyId>) { export async function getDatabaseUsage(request: FastifyRequest<OnlyId>) {
@@ -233,9 +276,9 @@ export async function getDatabaseUsage(request: FastifyRequest<OnlyId>) {
} }
return { return {
usage usage
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function startDatabase(request: FastifyRequest<OnlyId>) { export async function startDatabase(request: FastifyRequest<OnlyId>) {
@@ -282,7 +325,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
volumes: [volume], volumes: [volume],
ulimits, ulimits,
labels, labels,
...defaultComposeConfiguration(network), ...defaultComposeConfiguration(network)
} }
}, },
networks: { networks: {
@@ -292,18 +335,20 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
}, },
volumes: { volumes: {
[volumeName]: { [volumeName]: {
name: volumeName, name: volumeName
} }
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await executeCommand({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` }) await executeCommand({
dockerId: destinationDocker.id,
command: `docker compose -f ${composeFileDestination} up -d`
});
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
return {}; return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function stopDatabase(request: FastifyRequest<OnlyId>) { export async function stopDatabase(request: FastifyRequest<OnlyId>) {
@@ -326,34 +371,42 @@ export async function stopDatabase(request: FastifyRequest<OnlyId>) {
}); });
await prisma.database.update({ where: { id }, data: { publicPort: null } }); await prisma.database.update({ where: { id }, data: { publicPort: null } });
return {}; return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) { export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) {
try { try {
const { id } = request.params; const { id } = request.params;
let { since = 0 } = request.query let { since = 0 } = request.query;
if (since !== 0) { if (since !== 0) {
since = day(since).unix(); since = day(since).unix();
} }
const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ const {
destinationDockerId,
destinationDocker: { id: dockerId }
} = await prisma.database.findUnique({
where: { id }, where: { id },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
if (destinationDockerId) { if (destinationDockerId) {
try { try {
// const found = await checkContainer({ dockerId, container: id }) const { default: ansi } = await import('strip-ansi');
// if (found) { const { stdout, stderr } = await executeCommand({
const { default: ansi } = await import('strip-ansi') dockerId,
const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` }) command: `docker logs --since ${since} --tail 5000 --timestamps ${id}`
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a); });
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a); const stripLogsStdout = stdout
const logs = stripLogsStderr.concat(stripLogsStdout) .toString()
const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1)) .split('\n')
return { logs: sortedLogs } .map((l) => ansi(l))
// } .filter((a) => a);
const stripLogsStderr = stderr
.toString()
.split('\n')
.map((l) => ansi(l))
.filter((a) => a);
return { logs: stripLogsStderr.concat(stripLogsStdout) };
} catch (error) { } catch (error) {
const { statusCode } = error; const { statusCode } = error;
if (statusCode === 404) { if (statusCode === 404) {
@@ -365,9 +418,9 @@ export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>)
} }
return { return {
message: 'No logs found.' message: 'No logs found.'
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) { export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) {
@@ -384,15 +437,16 @@ export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) {
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
if (database.destinationDockerId) { if (database.destinationDockerId) {
const everStarted = await stopDatabaseContainer(database); const everStarted = await stopDatabaseContainer(database);
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort); if (everStarted)
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
} }
} }
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } }); await prisma.database.delete({ where: { id } });
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveDatabase(request: FastifyRequest<SaveDatabase>, reply: FastifyReply) { export async function saveDatabase(request: FastifyRequest<SaveDatabase>, reply: FastifyReply) {
@@ -436,9 +490,9 @@ export async function saveDatabase(request: FastifyRequest<SaveDatabase>, reply:
version version
} }
}); });
return reply.code(201).send({}) return reply.code(201).send({});
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseSettings>) { export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseSettings>) {
@@ -447,9 +501,11 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
const { id } = request.params; const { id } = request.params;
const { isPublic, appendOnly = true } = request.body; const { isPublic, appendOnly = true } = request.body;
let publicPort = null let publicPort = null;
const { destinationDocker: { remoteEngine, engine, remoteIpAddress } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }) const {
destinationDocker: { remoteEngine, engine, remoteIpAddress }
} = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } });
if (isPublic) { if (isPublic) {
publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress });
@@ -480,14 +536,14 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
await stopTcpHttpProxy(id, destinationDocker, oldPublicPort); await stopTcpHttpProxy(id, destinationDocker, oldPublicPort);
} }
} }
return { publicPort } return { publicPort };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getDatabaseSecrets(request: FastifyRequest<OnlyId>) { export async function getDatabaseSecrets(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params;
let secrets = await prisma.databaseSecret.findMany({ let secrets = await prisma.databaseSecret.findMany({
where: { databaseId: id }, where: { databaseId: id },
orderBy: { createdAt: 'desc' } orderBy: { createdAt: 'desc' }
@@ -499,21 +555,24 @@ export async function getDatabaseSecrets(request: FastifyRequest<OnlyId>) {
return { return {
secrets secrets
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveDatabaseSecret(request: FastifyRequest<SaveDatabaseSecret>, reply: FastifyReply) { export async function saveDatabaseSecret(
request: FastifyRequest<SaveDatabaseSecret>,
reply: FastifyReply
) {
try { try {
const { id } = request.params const { id } = request.params;
let { name, value, isNew } = request.body let { name, value, isNew } = request.body;
if (isNew) { if (isNew) {
const found = await prisma.databaseSecret.findFirst({ where: { name, databaseId: id } }); const found = await prisma.databaseSecret.findFirst({ where: { name, databaseId: id } });
if (found) { if (found) {
throw `Secret ${name} already exists.` throw `Secret ${name} already exists.`;
} else { } else {
value = encrypt(value.trim()); value = encrypt(value.trim());
await prisma.databaseSecret.create({ await prisma.databaseSecret.create({
@@ -535,18 +594,18 @@ export async function saveDatabaseSecret(request: FastifyRequest<SaveDatabaseSec
}); });
} }
} }
return reply.code(201).send() return reply.code(201).send();
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function deleteDatabaseSecret(request: FastifyRequest<DeleteDatabaseSecret>) { export async function deleteDatabaseSecret(request: FastifyRequest<DeleteDatabaseSecret>) {
try { try {
const { id } = request.params const { id } = request.params;
const { name } = request.body const { name } = request.body;
await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } }); await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } });
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -561,12 +561,7 @@ export const applicationsRouter = router({
.split('\n') .split('\n')
.map((l) => ansi(l)) .map((l) => ansi(l))
.filter((a) => a); .filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout); return { logs: stripLogsStderr.concat(stripLogsStdout) };
const sortedLogs = logs.sort((a, b) =>
day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1
);
return { logs: sortedLogs };
// }
} catch (error) { } catch (error) {
const { statusCode, stderr } = error; const { statusCode, stderr } = error;
if (stderr.startsWith('Error: No such container')) { if (stderr.startsWith('Error: No such container')) {

View File

@@ -68,16 +68,11 @@ export const servicesRouter = router({
.split('\n') .split('\n')
.map((l) => ansi(l)) .map((l) => ansi(l))
.filter((a) => a); .filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout);
const sortedLogs = logs.sort((a, b) =>
day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1
);
return { return {
data: { data: {
logs: sortedLogs logs: stripLogsStderr.concat(stripLogsStdout)
} }
}; };
// }
} catch (error) { } catch (error) {
const { statusCode, stderr } = error; const { statusCode, stderr } = error;
if (stderr.startsWith('Error: No such container')) { if (stderr.startsWith('Error: No such container')) {
@@ -92,7 +87,6 @@ export const servicesRouter = router({
return { return {
data: { data: {
logs: [] logs: []
} }
}; };
} }