diff --git a/striker-ui-api/src/app.ts b/striker-ui-api/src/app.ts index 32f212b6..c50193ca 100644 --- a/striker-ui-api/src/app.ts +++ b/striker-ui-api/src/app.ts @@ -7,25 +7,27 @@ import routes from './routes'; import { rrouters } from './lib/rrouters'; import session from './session'; -const app = express(); +export default (async () => { + const app = express(); -app.use(json()); + app.use(json()); -app.use(cors()); + app.use(cors()); -// Add session handler to the chain **after** adding other handlers that do -// not depend on session(s). -app.use(session); + // Add session handler to the chain **after** adding other handlers that do + // not depend on session(s). + app.use(await session); -app.use(passport.initialize()); -app.use(passport.authenticate('session')); + app.use(passport.initialize()); + app.use(passport.authenticate('session')); -rrouters(app, routes.private, { - assign: (router) => [guardApi, router], - route: '/api', -}); -rrouters(app, routes.public, { route: '/api' }); + rrouters(app, routes.private, { + assign: (router) => [guardApi, router], + route: '/api', + }); + rrouters(app, routes.public, { route: '/api' }); -app.use(routes.static); + app.use(routes.static); -export default app; + return app; +})(); diff --git a/striker-ui-api/src/index.ts b/striker-ui-api/src/index.ts index 566f927c..8cfe2903 100644 --- a/striker-ui-api/src/index.ts +++ b/striker-ui-api/src/index.ts @@ -5,20 +5,22 @@ import { PGID, PUID, PORT, ECODE_DROP_PRIVILEGES } from './lib/consts'; import app from './app'; import { stderr, stdout } from './lib/shell'; -stdout(`Starting process with ownership ${getuid()}:${getgid()}`); +(async () => { + stdout(`Starting process with ownership ${getuid()}:${getgid()}`); -app.listen(PORT, () => { - try { - // Group must be set before user to avoid permission error. - setgid(PGID); - setuid(PUID); + (await app).listen(PORT, () => { + try { + // Group must be set before user to avoid permission error. + setgid(PGID); + setuid(PUID); - stdout(`Process ownership changed to ${getuid()}:${getgid()}.`); - } catch (error) { - stderr(`Failed to change process ownership; CAUSE: ${error}`); + stdout(`Process ownership changed to ${getuid()}:${getgid()}.`); + } catch (error) { + stderr(`Failed to change process ownership; CAUSE: ${error}`); - process.exit(ECODE_DROP_PRIVILEGES); - } + process.exit(ECODE_DROP_PRIVILEGES); + } - stdout(`Listening on localhost:${PORT}.`); -}); + stdout(`Listening on localhost:${PORT}.`); + }); +})(); diff --git a/striker-ui-api/src/lib/accessModule.ts b/striker-ui-api/src/lib/accessModule.ts index ee710981..f33c2cb9 100644 --- a/striker-ui-api/src/lib/accessModule.ts +++ b/striker-ui-api/src/lib/accessModule.ts @@ -1,11 +1,144 @@ -import { spawn, spawnSync, SpawnSyncOptions } from 'child_process'; +import { + ChildProcess, + spawn, + SpawnOptions, + spawnSync, + SpawnSyncOptions, +} from 'child_process'; +import EventEmitter from 'events'; import { readFileSync } from 'fs'; -import { SERVER_PATHS } from './consts'; +import { SERVER_PATHS, PGID, PUID } from './consts'; import { formatSql } from './formatSql'; import { isObject } from './isObject'; -import { date, stderr as sherr, stdout as shout } from './shell'; +import { + date, + stderr as sherr, + stdout as shout, + stdoutVar as shvar, + uuid, +} from './shell'; + +type AccessStartOptions = { + args?: readonly string[]; +} & SpawnOptions; + +class Access extends EventEmitter { + private ps: ChildProcess; + private queue: string[] = []; + + constructor({ + eventEmitterOptions = {}, + spawnOptions = {}, + }: { + eventEmitterOptions?: ConstructorParameters[0]; + spawnOptions?: SpawnOptions; + } = {}) { + super(eventEmitterOptions); + + this.ps = this.start(spawnOptions); + } + + private start({ + args = [], + gid = PGID, + stdio = 'pipe', + timeout = 10000, + uid = PUID, + ...restSpawnOptions + }: AccessStartOptions = {}) { + shvar({ gid, stdio, timeout, uid, ...restSpawnOptions }); + + const ps = spawn(SERVER_PATHS.usr.sbin['anvil-access-module'].self, args, { + gid, + stdio, + timeout, + uid, + ...restSpawnOptions, + }); + + let stderr = ''; + let stdout = ''; + + ps.stderr?.setEncoding('utf-8').on('data', (chunk: string) => { + stderr += chunk; + + const scriptId = this.queue.at(0); + + if (scriptId) { + sherr(`${Access.event(scriptId, 'stderr')}: ${stderr}`); + + stderr = ''; + } + }); + + ps.stdout?.setEncoding('utf-8').on('data', (chunk: string) => { + stdout += chunk; + + let nindex: number = stdout.indexOf('\n'); + + // 1. ~a is the shorthand for -(a + 1) + // 2. negatives are evaluated to true + while (~nindex) { + const scriptId = this.queue.shift(); + + if (scriptId) this.emit(scriptId, stdout.substring(0, nindex)); + + stdout = stdout.substring(nindex + 1); + nindex = stdout.indexOf('\n'); + } + }); + + return ps; + } + + private stop() { + this.ps.once('error', () => !this.ps.killed && this.ps.kill('SIGKILL')); + + this.ps.kill(); + } + + private restart(options?: AccessStartOptions) { + this.ps.once('close', () => this.start(options)); + + this.stop(); + } + + private static event(scriptId: string, category: 'stderr'): string { + return `${scriptId}-${category}`; + } + + public interact(command: string, ...args: string[]) { + const { stdin } = this.ps; + + const scriptId = uuid(); + const script = `${command} ${args.join(' ')}\n`; + + const promise = new Promise((resolve, reject) => { + this.once(scriptId, (data) => { + let result: T; + + try { + result = JSON.parse(data); + } catch (error) { + return reject(`Failed to parse line ${scriptId}; got [${data}]`); + } + + return resolve(result); + }); + }); + + shvar({ scriptId, script }); + + this.queue.push(scriptId); + stdin?.write(script); + + return promise; + } +} + +const access = new Access(); const asyncAnvilAccessModule = ( args: string[], @@ -171,11 +304,8 @@ const dbJobAnvilSyncShared = ( return dbInsertOrUpdateJob(subParams); }; -const dbQuery = (query: string, options?: SpawnSyncOptions) => { - shout(formatSql(query)); - - return execAnvilAccessModule(['--query', query], options); -}; +const query = (sqlscript: string) => + access.interact('r', formatSql(sqlscript)); const dbSubRefreshTimestamp = () => { let result: string; @@ -304,12 +434,12 @@ export { dbInsertOrUpdateJob as job, dbInsertOrUpdateVariable as variable, dbJobAnvilSyncShared, - dbQuery, dbSubRefreshTimestamp as timestamp, dbWrite, + execModuleSubroutine as sub, getAnvilData, getLocalHostName, getLocalHostUUID, getPeerData, - execModuleSubroutine as sub, + query, }; diff --git a/striker-ui-api/src/lib/getSessionSecret.ts b/striker-ui-api/src/lib/getSessionSecret.ts index f12caa8b..cc20ba04 100644 --- a/striker-ui-api/src/lib/getSessionSecret.ts +++ b/striker-ui-api/src/lib/getSessionSecret.ts @@ -2,18 +2,18 @@ import assert from 'assert'; import { ECODE_SESSION_SECRET, VNAME_SESSION_SECRET } from './consts'; -import { dbQuery, variable } from './accessModule'; +import { query, variable } from './accessModule'; import { openssl, stderr, stdout } from './shell'; -export const getSessionSecret = (): string => { +export const getSessionSecret = async (): Promise => { let sessionSecret: string; try { - const rows: [sessionSecret: string][] = dbQuery( + const rows: [sessionSecret: string][] = await query( `SELECT variable_value FROM variables WHERE variable_name = '${VNAME_SESSION_SECRET}';`, - ).stdout; + ); assert(rows.length > 0, 'No existing session secret found.'); diff --git a/striker-ui-api/src/lib/request_handlers/buildGetRequestHandler.ts b/striker-ui-api/src/lib/request_handlers/buildGetRequestHandler.ts index 2ddd6e32..051728c3 100644 --- a/striker-ui-api/src/lib/request_handlers/buildGetRequestHandler.ts +++ b/striker-ui-api/src/lib/request_handlers/buildGetRequestHandler.ts @@ -1,64 +1,53 @@ import { Request, Response } from 'express'; -import { dbQuery } from '../accessModule'; +import { query } from '../accessModule'; import call from '../call'; +import { stderr, stdout, stdoutVar } from '../shell'; const buildGetRequestHandler = ( - query: string | BuildQueryFunction, + sqlscript: string | BuildQueryFunction, { beforeRespond }: BuildGetRequestHandlerOptions = {}, ) => - (request: Request, response: Response) => { - console.log('Calling CLI script to get data.'); + async (request: Request, response: Response) => { + stdout('Calling CLI script to get data.'); const buildQueryOptions: BuildQueryOptions = {}; - let queryStdout; + let result: (number | null | string)[][]; try { - ({ stdout: queryStdout } = dbQuery( - call(query, { + result = await query( + call(sqlscript, { parameters: [request, buildQueryOptions], - notCallableReturn: query, + notCallableReturn: sqlscript, }), - )); + ); } catch (queryError) { - console.log(`Failed to execute query; CAUSE: ${queryError}`); + stderr(`Failed to execute query; CAUSE: ${queryError}`); response.status(500).send(); return; } - console.log( - `Query stdout pre-hooks (type=[${typeof queryStdout}]): ${JSON.stringify( - queryStdout, - null, - 2, - )}`, - ); + stdoutVar(result, `Query stdout pre-hooks (type=[${typeof result}]): `); const { afterQueryReturn } = buildQueryOptions; - queryStdout = call(afterQueryReturn, { - parameters: [queryStdout], - notCallableReturn: queryStdout, + result = call(afterQueryReturn, { + parameters: [result], + notCallableReturn: result, }); - queryStdout = call(beforeRespond, { - parameters: [queryStdout], - notCallableReturn: queryStdout, + result = call(beforeRespond, { + parameters: [result], + notCallableReturn: result, }); - console.log( - `Query stdout post-hooks (type=[${typeof queryStdout}]): ${JSON.stringify( - queryStdout, - null, - 2, - )}`, - ); + stdoutVar(result, `Query stdout post-hooks (type=[${typeof result}]): `); - response.json(queryStdout); + response.json(result); }; export default buildGetRequestHandler; diff --git a/striker-ui-api/src/lib/request_handlers/command/getHostSSH.ts b/striker-ui-api/src/lib/request_handlers/command/getHostSSH.ts index 258d9f49..7bf007bb 100644 --- a/striker-ui-api/src/lib/request_handlers/command/getHostSSH.ts +++ b/striker-ui-api/src/lib/request_handlers/command/getHostSSH.ts @@ -1,71 +1,68 @@ +import assert from 'assert'; import { RequestHandler } from 'express'; +import { REP_IPV4, REP_PEACEFUL_STRING } from '../../consts'; import { HOST_KEY_CHANGED_PREFIX } from '../../consts/HOST_KEY_CHANGED_PREFIX'; -import { dbQuery, getLocalHostUUID, getPeerData } from '../../accessModule'; -import { sanitizeSQLParam } from '../../sanitizeSQLParam'; +import { getLocalHostUUID, getPeerData, query } from '../../accessModule'; +import { sanitize } from '../../sanitize'; import { stderr } from '../../shell'; export const getHostSSH: RequestHandler< unknown, - { - badSSHKeys?: DeleteSshKeyConflictRequestBody; - hostName: string; - hostOS: string; - hostUUID: string; - isConnected: boolean; - isInetConnected: boolean; - isOSRegistered: boolean; - }, - { - password: string; - port?: number; - ipAddress: string; - } -> = (request, response) => { + GetHostSshResponseBody, + GetHostSshRequestBody +> = async (request, response) => { const { - body: { password, port = 22, ipAddress: target }, + body: { password: rpassword, port: rport = 22, ipAddress: rtarget }, } = request; - let hostName: string; - let hostOS: string; - let hostUUID: string; - let isConnected: boolean; - let isInetConnected: boolean; - let isOSRegistered: boolean; + const password = sanitize(rpassword, 'string'); + const port = sanitize(rport, 'number'); + const target = sanitize(rtarget, 'string', { modifierType: 'sql' }); + + try { + assert( + REP_PEACEFUL_STRING.test(password), + `Password must be a peaceful string; got [${password}]`, + ); + + assert( + Number.isInteger(port), + `Port must be a valid integer; got [${port}]`, + ); + + assert( + REP_IPV4.test(target), + `IP address must be a valid IPv4 address; got [${target}]`, + ); + } catch (assertError) { + stderr(`Assert failed when getting host SSH data; CAUSE: ${assertError}`); + + return response.status(400).send(); + } const localHostUUID = getLocalHostUUID(); + let rsbody: GetHostSshResponseBody; + try { - ({ - hostName, - hostOS, - hostUUID, - isConnected, - isInetConnected, - isOSRegistered, - } = getPeerData(target, { password, port })); + rsbody = getPeerData(target, { password, port }); } catch (subError) { stderr(`Failed to get peer data; CAUSE: ${subError}`); - response.status(500).send(); - - return; + return response.status(500).send(); } - let badSSHKeys: DeleteSshKeyConflictRequestBody | undefined; - - if (!isConnected) { - const rows = dbQuery(` + if (!rsbody.isConnected) { + const rows: [stateNote: string, stateUUID: string][] = await query(` SELECT sta.state_note, sta.state_uuid FROM states AS sta WHERE sta.state_host_uuid = '${localHostUUID}' - AND sta.state_name = '${HOST_KEY_CHANGED_PREFIX}${sanitizeSQLParam( - target, - )}';`).stdout as [stateNote: string, stateUUID: string][]; + AND sta.state_name = '${HOST_KEY_CHANGED_PREFIX}${target}';`); if (rows.length > 0) { - badSSHKeys = rows.reduce( + rsbody.badSSHKeys = rows.reduce( (previous, [, stateUUID]) => { previous[localHostUUID].push(stateUUID); @@ -76,13 +73,5 @@ export const getHostSSH: RequestHandler< } } - response.status(200).send({ - badSSHKeys, - hostName, - hostOS, - hostUUID, - isConnected, - isInetConnected, - isOSRegistered, - }); + response.status(200).send(rsbody); }; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts index d3a87085..8a02b345 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; -import { dbQuery, getLocalHostName } from '../../accessModule'; +import { getLocalHostName, query } from '../../accessModule'; import { getHostNameDomain, getHostNamePrefix, @@ -8,17 +8,18 @@ import { } from '../../disassembleHostName'; import { stderr } from '../../shell'; -export const getManifestTemplate: RequestHandler = (request, response) => { - let localHostName = ''; +export const getManifestTemplate: RequestHandler = async ( + request, + response, +) => { + let localHostName: string; try { localHostName = getLocalHostName(); } catch (subError) { stderr(String(subError)); - response.status(500).send(); - - return; + return response.status(500).send(); } const localShortHostName = getShortHostName(localHostName); @@ -38,7 +39,7 @@ export const getManifestTemplate: RequestHandler = (request, response) => { >; try { - ({ stdout: rawQueryResult } = dbQuery( + rawQueryResult = await query( `SELECT a.fence_uuid, a.fence_name, @@ -71,13 +72,11 @@ export const getManifestTemplate: RequestHandler = (request, response) => { ORDER BY manifest_name DESC LIMIT 1 ) AS c ON a.row_number = c.row_number;`, - )); + ); } catch (queryError) { stderr(`Failed to execute query; CAUSE: ${queryError}`); - response.status(500).send(); - - return; + return response.status(500).send(); } const queryResult = rawQueryResult.reduce< diff --git a/striker-ui-api/src/lib/request_handlers/server/createServer.ts b/striker-ui-api/src/lib/request_handlers/server/createServer.ts index cae0d884..a5b48c0a 100644 --- a/striker-ui-api/src/lib/request_handlers/server/createServer.ts +++ b/striker-ui-api/src/lib/request_handlers/server/createServer.ts @@ -1,107 +1,120 @@ import assert from 'assert'; import { RequestHandler } from 'express'; +import { REP_UUID, SERVER_PATHS } from '../../consts'; import { OS_LIST_MAP } from '../../consts/OS_LIST'; -import { REP_INTEGER, REP_UUID } from '../../consts/REG_EXP_PATTERNS'; -import SERVER_PATHS from '../../consts/SERVER_PATHS'; -import { dbQuery, job } from '../../accessModule'; -import { stderr, stdout } from '../../shell'; +import { job, query } from '../../accessModule'; +import { sanitize } from '../../sanitize'; +import { stderr, stdout, stdoutVar } from '../../shell'; -export const createServer: RequestHandler = ({ body }, response) => { - stdout(`Creating server.\n${JSON.stringify(body, null, 2)}`); +export const createServer: RequestHandler = async (request, response) => { + const { body: rqbody = {} } = request; + + stdoutVar({ rqbody }, 'Creating server.\n'); const { - serverName, - cpuCores, - memory, + serverName: rServerName, + cpuCores: rCpuCores, + memory: rMemory, virtualDisks: [ - { storageSize = undefined, storageGroupUUID = undefined } = {}, + { + storageSize: rStorageSize = undefined, + storageGroupUUID: rStorageGroupUuid = undefined, + } = {}, ] = [], - installISOFileUUID, - driverISOFileUUID, - anvilUUID, - optimizeForOS, - } = body || {}; - - const dataServerName = String(serverName); - const dataOS = String(optimizeForOS); - const dataCPUCores = String(cpuCores); - const dataRAM = String(memory); - const dataStorageGroupUUID = String(storageGroupUUID); - const dataStorageSize = String(storageSize); - const dataInstallISO = String(installISOFileUUID); - const dataDriverISO = String(driverISOFileUUID) || 'none'; - const dataAnvilUUID = String(anvilUUID); + installISOFileUUID: rInstallIsoUuid, + driverISOFileUUID: rDriverIsoUuid, + anvilUUID: rAnvilUuid, + optimizeForOS: rOptimizeForOs, + } = rqbody; + + const serverName = sanitize(rServerName, 'string'); + const os = sanitize(rOptimizeForOs, 'string'); + const cpuCores = sanitize(rCpuCores, 'number'); + const memory = sanitize(rMemory, 'number'); + const storageGroupUUID = sanitize(rStorageGroupUuid, 'string'); + const storageSize = sanitize(rStorageSize, 'number'); + const installIsoUuid = sanitize(rInstallIsoUuid, 'string'); + const driverIsoUuid = sanitize(rDriverIsoUuid, 'string', { + fallback: 'none', + }); + const anvilUuid = sanitize(rAnvilUuid, 'string'); try { assert( - /^[0-9a-z_-]+$/i.test(dataServerName), - `Data server name can only contain alphanumeric, underscore, and hyphen characters; got [${dataServerName}].`, + /^[0-9a-z_-]+$/i.test(serverName), + `Data server name can only contain alphanumeric, underscore, and hyphen characters; got [${serverName}]`, ); - const [[serverNameCount]] = dbQuery( - `SELECT COUNT(server_uuid) FROM servers WHERE server_name = '${dataServerName}'`, - ).stdout; + const [[serverNameCount]] = await query( + `SELECT COUNT(server_uuid) FROM servers WHERE server_name = '${serverName}'`, + ); assert( serverNameCount === 0, - `Data server name already exists; got [${dataServerName}]`, + `Data server name already exists; got [${serverName}]`, ); + assert( - OS_LIST_MAP[dataOS] !== undefined, - `Data OS not recognized; got [${dataOS}].`, + OS_LIST_MAP[os] !== undefined, + `Data OS not recognized; got [${os}]`, ); + assert( - REP_INTEGER.test(dataCPUCores), - `Data CPU cores can only contain digits; got [${dataCPUCores}].`, + Number.isInteger(cpuCores), + `Data CPU cores can only contain digits; got [${cpuCores}]`, ); + assert( - REP_INTEGER.test(dataRAM), - `Data RAM can only contain digits; got [${dataRAM}].`, + Number.isInteger(memory), + `Data RAM can only contain digits; got [${memory}]`, ); + assert( - REP_UUID.test(dataStorageGroupUUID), - `Data storage group UUID must be a valid UUID; got [${dataStorageGroupUUID}].`, + REP_UUID.test(storageGroupUUID), + `Data storage group UUID must be a valid UUID; got [${storageGroupUUID}]`, ); + assert( - REP_INTEGER.test(dataStorageSize), - `Data storage size can only contain digits; got [${dataStorageSize}].`, + Number.isInteger(storageSize), + `Data storage size can only contain digits; got [${storageSize}]`, ); + assert( - REP_UUID.test(dataInstallISO), - `Data install ISO must be a valid UUID; got [${dataInstallISO}].`, + REP_UUID.test(installIsoUuid), + `Data install ISO must be a valid UUID; got [${installIsoUuid}]`, ); + assert( - dataDriverISO === 'none' || REP_UUID.test(dataDriverISO), - `Data driver ISO must be a valid UUID when provided; got [${dataDriverISO}].`, + driverIsoUuid === 'none' || REP_UUID.test(driverIsoUuid), + `Data driver ISO must be a valid UUID when provided; got [${driverIsoUuid}]`, ); + assert( - REP_UUID.test(dataAnvilUUID), - `Data anvil UUID must be a valid UUID; got [${dataAnvilUUID}].`, + REP_UUID.test(anvilUuid), + `Data anvil UUID must be a valid UUID; got [${anvilUuid}]`, ); } catch (assertError) { stdout( - `Failed to assert value when trying to provision a server; CAUSE: ${assertError}.`, + `Failed to assert value when trying to provision a server; CAUSE: ${assertError}`, ); - response.status(400).send(); - - return; + return response.status(400).send(); } - const provisionServerJobData = `server_name=${dataServerName} -os=${dataOS} -cpu_cores=${dataCPUCores} -ram=${dataRAM} -storage_group_uuid=${dataStorageGroupUUID} -storage_size=${dataStorageSize} -install_iso=${dataInstallISO} -driver_iso=${dataDriverISO}`; + const provisionServerJobData = `server_name=${serverName} +os=${os} +cpu_cores=${cpuCores} +ram=${memory} +storage_group_uuid=${storageGroupUUID} +storage_size=${storageSize} +install_iso=${installIsoUuid} +driver_iso=${driverIsoUuid}`; stdout(`provisionServerJobData=[${provisionServerJobData}]`); - const [[provisionServerJobHostUUID]] = dbQuery( + const [[provisionServerJobHostUUID]]: [[string]] = await query( `SELECT CASE WHEN pri_hos.primary_host_uuid IS NULL @@ -120,7 +133,7 @@ driver_iso=${dataDriverISO}`; AND sca_clu_nod.scan_cluster_node_crmd_member AND sca_clu_nod.scan_cluster_node_cluster_member AND (NOT sca_clu_nod.scan_cluster_node_maintenance_mode) - AND anv.anvil_uuid = '${dataAnvilUUID}' + AND anv.anvil_uuid = '${anvilUuid}' ORDER BY sca_clu_nod.scan_cluster_node_name LIMIT 1 ) AS pri_hos @@ -129,10 +142,10 @@ driver_iso=${dataDriverISO}`; 1 AS phr, anv.anvil_node1_host_uuid AS node1_host_uuid FROM anvils AS anv - WHERE anv.anvil_uuid = '${dataAnvilUUID}' + WHERE anv.anvil_uuid = '${anvilUuid}' ) AS nod_1 ON pri_hos.phl = nod_1.phr;`, - ).stdout; + ); stdout(`provisionServerJobHostUUID=[${provisionServerJobHostUUID}]`); @@ -149,9 +162,7 @@ driver_iso=${dataDriverISO}`; } catch (subError) { stderr(`Failed to provision server; CAUSE: ${subError}`); - response.status(500).send(); - - return; + return response.status(500).send(); } response.status(202).send(); diff --git a/striker-ui-api/src/lib/request_handlers/server/getServerDetail.ts b/striker-ui-api/src/lib/request_handlers/server/getServerDetail.ts index c847133e..a9641700 100644 --- a/striker-ui-api/src/lib/request_handlers/server/getServerDetail.ts +++ b/striker-ui-api/src/lib/request_handlers/server/getServerDetail.ts @@ -3,10 +3,9 @@ import { RequestHandler } from 'express'; import { createReadStream } from 'fs'; import path from 'path'; -import { REP_UUID } from '../../consts/REG_EXP_PATTERNS'; -import SERVER_PATHS from '../../consts/SERVER_PATHS'; +import { REP_UUID, SERVER_PATHS } from '../../consts'; -import { dbQuery, getLocalHostUUID, job } from '../../accessModule'; +import { getLocalHostUUID, job, query } from '../../accessModule'; import { sanitize } from '../../sanitize'; import { mkfifo, rm, stderr, stdout } from '../../shell'; @@ -18,7 +17,7 @@ const rmfifo = (path: string) => { } }; -export const getServerDetail: RequestHandler = (request, response) => { +export const getServerDetail: RequestHandler = async (request, response) => { const { serverUUID } = request.params; const { ss, resize } = request.query; @@ -39,9 +38,7 @@ export const getServerDetail: RequestHandler = (request, response) => { `Failed to assert value when trying to get server detail; CAUSE: ${assertError}.`, ); - response.status(500).send(); - - return; + return response.status(500).send(); } if (isScreenshot) { @@ -52,24 +49,20 @@ export const getServerDetail: RequestHandler = (request, response) => { } catch (subError) { stderr(String(subError)); - response.status(500).send(); - - return; + return response.status(500).send(); } stdout(`requestHostUUID=[${requestHostUUID}]`); try { - [[serverHostUUID]] = dbQuery(` + [[serverHostUUID]] = await query(` SELECT server_host_uuid FROM servers - WHERE server_uuid = '${serverUUID}';`).stdout; + WHERE server_uuid = '${serverUUID}';`); } catch (queryError) { stderr(`Failed to get server host UUID; CAUSE: ${queryError}`); - response.status(500).send(); - - return; + return response.status(500).send(); } stdout(`serverHostUUID=[${serverHostUUID}]`); @@ -94,9 +87,9 @@ export const getServerDetail: RequestHandler = (request, response) => { namedPipeReadStream.once('close', () => { stdout(`On close; removing named pipe at ${imageFilePath}.`); - response.status(200).send({ screenshot: imageData }); - rmfifo(imageFilePath); + + return response.status(200).send({ screenshot: imageData }); }); namedPipeReadStream.on('data', (data) => { @@ -123,11 +116,9 @@ export const getServerDetail: RequestHandler = (request, response) => { `Failed to prepare named pipe and/or receive image data; CAUSE: ${prepPipeError}`, ); - response.status(500).send(); - rmfifo(imageFilePath); - return; + return response.status(500).send(); } let resizeArgs = sanitize(resize, 'string'); @@ -152,9 +143,7 @@ out-file-id=${epoch}`, } catch (subError) { stderr(`Failed to queue fetch server screenshot job; CAUSE: ${subError}`); - response.status(500).send(); - - return; + return response.status(500).send(); } } else { // For getting sever detail data. diff --git a/striker-ui-api/src/routes/file.ts b/striker-ui-api/src/routes/file.ts index bfcb63ff..6c348001 100644 --- a/striker-ui-api/src/routes/file.ts +++ b/striker-ui-api/src/routes/file.ts @@ -1,31 +1,33 @@ import express from 'express'; +import { DELETED } from '../lib/consts'; + import { dbJobAnvilSyncShared, - dbQuery, timestamp, dbWrite, + query, } from '../lib/accessModule'; import getFile from '../lib/request_handlers/file/getFile'; import getFileDetail from '../lib/request_handlers/file/getFileDetail'; import uploadSharedFiles from '../middlewares/uploadSharedFiles'; +import { stderr, stdout, stdoutVar } from '../lib/shell'; const router = express.Router(); router - .delete('/:fileUUID', (request, response) => { + .delete('/:fileUUID', async (request, response) => { const { fileUUID } = request.params; - const FILE_TYPE_DELETED = 'DELETED'; - const [[oldFileType]] = dbQuery( + const [[oldFileType]] = await query( `SELECT file_type FROM files WHERE file_uuid = '${fileUUID}';`, - ).stdout; + ); - if (oldFileType !== FILE_TYPE_DELETED) { + if (oldFileType !== DELETED) { dbWrite( `UPDATE files SET - file_type = '${FILE_TYPE_DELETED}', + file_type = '${DELETED}', modified_date = '${timestamp()}' WHERE file_uuid = '${fileUUID}';`, ).stdout; @@ -40,11 +42,10 @@ router .get('/', getFile) .get('/:fileUUID', getFileDetail) .post('/', uploadSharedFiles.single('file'), ({ file, body }, response) => { - console.log('Receiving shared file.'); + stdout('Receiving shared file.'); if (file) { - console.log(`file: ${JSON.stringify(file, null, 2)}`); - console.log(`body: ${JSON.stringify(body, null, 2)}`); + stdoutVar({ body, file }); dbJobAnvilSyncShared( 'move_incoming', @@ -56,24 +57,26 @@ router response.status(200).send(); } }) - .put('/:fileUUID', (request, response) => { - console.log('Begin edit single file.'); - console.dir(request.body); + .put('/:fileUUID', async (request, response) => { + const { body = {}, params } = request; - const { fileUUID } = request.params; - const { fileName, fileLocations, fileType } = request.body; + stdoutVar(body, 'Begin edit single file. body='); + + const { fileUUID } = params; + const { fileName, fileLocations, fileType } = body; const anvilSyncSharedFunctions = []; - let query = ''; + let sqlscript = ''; if (fileName) { - const [[oldFileName]] = dbQuery( + const [[oldFileName]] = await query( `SELECT file_name FROM files WHERE file_uuid = '${fileUUID}';`, - ).stdout; - console.log(`oldFileName=[${oldFileName}],newFileName=[${fileName}]`); + ); + + stdoutVar({ oldFileName, fileName }); if (fileName !== oldFileName) { - query += ` + sqlscript += ` UPDATE files SET file_name = '${fileName}', @@ -93,7 +96,7 @@ router } if (fileType) { - query += ` + sqlscript += ` UPDATE files SET file_type = '${fileType}', @@ -113,7 +116,7 @@ router if (fileLocations) { fileLocations.forEach( - ({ + async ({ fileLocationUUID, isFileLocationActive, }: { @@ -132,14 +135,18 @@ router jobDescription = '0133'; } - query += ` - UPDATE file_locations - SET - file_location_active = '${fileLocationActive}', - modified_date = '${timestamp()}' - WHERE file_location_uuid = '${fileLocationUUID}';`; - - const targetHosts = dbQuery( + sqlscript += ` + UPDATE file_locations + SET + file_location_active = '${fileLocationActive}', + modified_date = '${timestamp()}' + WHERE file_location_uuid = '${fileLocationUUID}';`; + + const targetHosts: [ + n1uuid: string, + n2uuid: string, + dr1uuid: null | string, + ][] = await query( `SELECT anv.anvil_node1_host_uuid, anv.anvil_node2_host_uuid, @@ -148,9 +155,9 @@ router JOIN file_locations AS fil_loc ON anv.anvil_uuid = fil_loc.file_location_anvil_uuid WHERE fil_loc.file_location_uuid = '${fileLocationUUID}';`, - ).stdout; + ); - targetHosts.flat().forEach((hostUUID: string) => { + targetHosts.flat().forEach((hostUUID: null | string) => { if (hostUUID) { anvilSyncSharedFunctions.push(() => dbJobAnvilSyncShared( @@ -167,34 +174,23 @@ router ); } - console.log(`Query (type=[${typeof query}]): [${query}]`); + stdout(`Query (type=[${typeof sqlscript}]): [${sqlscript}]`); let queryStdout; try { - ({ stdout: queryStdout } = dbWrite(query)); + ({ stdout: queryStdout } = dbWrite(sqlscript)); } catch (queryError) { - console.log(`Failed to execute query; CAUSE: ${queryError}`); + stderr(`Failed to execute query; CAUSE: ${queryError}`); - response.status(500).send(); + return response.status(500).send(); } - console.log( - `Query stdout (type=[${typeof queryStdout}]): ${JSON.stringify( - queryStdout, - null, - 2, - )}`, + stdoutVar(queryStdout, `Query stdout (type=[${typeof queryStdout}]): `); + + anvilSyncSharedFunctions.forEach((fn, index) => + stdoutVar(fn(), `Anvil sync shared [${index}] output: `), ); - anvilSyncSharedFunctions.forEach((fn, index) => { - console.log( - `Anvil sync shared [${index}] output: [${JSON.stringify( - fn(), - null, - 2, - )}]`, - ); - }); response.status(200).send(queryStdout); }); diff --git a/striker-ui-api/src/types/ApiCommand.d.ts b/striker-ui-api/src/types/ApiCommand.d.ts new file mode 100644 index 00000000..d3d03adf --- /dev/null +++ b/striker-ui-api/src/types/ApiCommand.d.ts @@ -0,0 +1,15 @@ +type GetHostSshRequestBody = { + password: string; + port?: number; + ipAddress: string; +}; + +type GetHostSshResponseBody = { + badSSHKeys?: DeleteSshKeyConflictRequestBody; + hostName: string; + hostOS: string; + hostUUID: string; + isConnected: boolean; + isInetConnected: boolean; + isOSRegistered: boolean; +};