commit
561fa1a9ec
320 changed files with 20465 additions and 1912 deletions
@ -0,0 +1,5 @@ |
||||
# dependencies |
||||
node_modules |
||||
|
||||
# output |
||||
out |
@ -0,0 +1,14 @@ |
||||
{ |
||||
"env": { |
||||
"es2022": true, |
||||
"node": true |
||||
}, |
||||
"extends": [ |
||||
"eslint:recommended", |
||||
"plugin:import/errors", |
||||
"plugin:import/typescript", |
||||
"plugin:import/warnings", |
||||
"prettier" |
||||
], |
||||
"plugins": ["import"] |
||||
} |
@ -0,0 +1,5 @@ |
||||
# dependencies |
||||
node_modules |
||||
|
||||
# output |
||||
out |
@ -1,18 +0,0 @@ |
||||
const cors = require('cors'); |
||||
const express = require('express'); |
||||
const path = require('path'); |
||||
|
||||
const API_ROOT_PATH = require('./lib/consts/API_ROOT_PATH'); |
||||
|
||||
const echoRouter = require('./routes/echo'); |
||||
const filesRouter = require('./routes/files'); |
||||
|
||||
const app = express(); |
||||
|
||||
app.use(express.json()); |
||||
app.use(cors()); |
||||
|
||||
app.use(path.join(API_ROOT_PATH, 'echo'), echoRouter); |
||||
app.use(path.join(API_ROOT_PATH, 'files'), filesRouter); |
||||
|
||||
module.exports = app; |
@ -1,94 +0,0 @@ |
||||
const { spawnSync } = require('child_process'); |
||||
|
||||
const SERVER_PATHS = require('./consts/SERVER_PATHS'); |
||||
|
||||
const execStrikerAccessDatabase = ( |
||||
args, |
||||
options = { |
||||
timeout: 10000, |
||||
encoding: 'utf-8', |
||||
}, |
||||
) => { |
||||
const { error, stdout, stderr } = spawnSync( |
||||
SERVER_PATHS.usr.sbin['striker-access-database'].self, |
||||
args, |
||||
options, |
||||
); |
||||
|
||||
if (error) { |
||||
throw error; |
||||
} |
||||
|
||||
if (stderr) { |
||||
throw new Error(stderr); |
||||
} |
||||
|
||||
let output; |
||||
|
||||
try { |
||||
output = JSON.parse(stdout); |
||||
} catch (stdoutParseError) { |
||||
output = stdout; |
||||
|
||||
console.warn( |
||||
`Failed to parse striker-access-database output [${output}]; error: [${stdoutParseError}]`, |
||||
); |
||||
} |
||||
|
||||
return { |
||||
stdout: output, |
||||
}; |
||||
}; |
||||
|
||||
const execDatabaseModuleSubroutine = (subName, subParams, options) => { |
||||
const args = ['--sub', subName]; |
||||
|
||||
if (subParams) { |
||||
args.push('--sub-params', JSON.stringify(subParams)); |
||||
} |
||||
|
||||
const { stdout } = execStrikerAccessDatabase(args, options); |
||||
|
||||
return { |
||||
stdout: stdout['sub_results'], |
||||
}; |
||||
}; |
||||
|
||||
const accessDB = { |
||||
dbJobAnvilSyncShared: ( |
||||
jobName, |
||||
jobData, |
||||
jobTitle, |
||||
jobDescription, |
||||
{ jobHostUUID } = { jobHostUUID: undefined }, |
||||
) => { |
||||
const subParams = { |
||||
file: __filename, |
||||
line: 0, |
||||
job_command: SERVER_PATHS.usr.sbin['anvil-sync-shared'].self, |
||||
job_data: jobData, |
||||
job_name: `storage::${jobName}`, |
||||
job_title: `job_${jobTitle}`, |
||||
job_description: `job_${jobDescription}`, |
||||
job_progress: 0, |
||||
}; |
||||
|
||||
if (jobHostUUID) { |
||||
subParams.job_host_uuid = jobHostUUID; |
||||
} |
||||
|
||||
console.log(JSON.stringify(subParams, null, 2)); |
||||
|
||||
return execDatabaseModuleSubroutine('insert_or_update_jobs', subParams) |
||||
.stdout; |
||||
}, |
||||
dbQuery: (query, options) => |
||||
execStrikerAccessDatabase(['--query', query], options), |
||||
dbSub: execDatabaseModuleSubroutine, |
||||
dbSubRefreshTimestamp: () => |
||||
execDatabaseModuleSubroutine('refresh_timestamp').stdout, |
||||
dbWrite: (query, options) => |
||||
execStrikerAccessDatabase(['--query', query, '--mode', 'write'], options), |
||||
}; |
||||
|
||||
module.exports = accessDB; |
@ -1,3 +0,0 @@ |
||||
const API_ROOT_PATH = '/api'; |
||||
|
||||
module.exports = API_ROOT_PATH; |
@ -1,34 +0,0 @@ |
||||
const path = require('path'); |
||||
|
||||
const SERVER_PATHS = { |
||||
mnt: { |
||||
shared: { |
||||
incoming: {}, |
||||
}, |
||||
}, |
||||
usr: { |
||||
sbin: { |
||||
'anvil-sync-shared': {}, |
||||
'striker-access-database': {}, |
||||
}, |
||||
}, |
||||
}; |
||||
|
||||
const generatePaths = ( |
||||
currentObject, |
||||
parents = path.parse(process.cwd()).root, |
||||
) => { |
||||
Object.keys(currentObject).forEach((pathKey) => { |
||||
const currentPath = path.join(parents, pathKey); |
||||
|
||||
currentObject[pathKey].self = currentPath; |
||||
|
||||
if (pathKey !== 'self') { |
||||
generatePaths(currentObject[pathKey], currentPath); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
generatePaths(SERVER_PATHS); |
||||
|
||||
module.exports = SERVER_PATHS; |
@ -1,29 +0,0 @@ |
||||
const { dbQuery } = require('../../accessDB'); |
||||
|
||||
const buildGetFiles = (query) => (request, response) => { |
||||
console.log('Calling CLI script to get data.'); |
||||
|
||||
let queryStdout; |
||||
|
||||
try { |
||||
({ stdout: queryStdout } = dbQuery( |
||||
typeof query === 'function' ? query(request) : query, |
||||
)); |
||||
} catch (queryError) { |
||||
console.log(`Query error: ${queryError}`); |
||||
|
||||
response.status(500).send(); |
||||
} |
||||
|
||||
console.log( |
||||
`Query stdout (type=[${typeof queryStdout}]): ${JSON.stringify( |
||||
queryStdout, |
||||
null, |
||||
2, |
||||
)}`,
|
||||
); |
||||
|
||||
response.json(queryStdout); |
||||
}; |
||||
|
||||
module.exports = buildGetFiles; |
@ -1,25 +0,0 @@ |
||||
const buildGetFiles = require('./buildGetFiles'); |
||||
|
||||
const getFileDetail = buildGetFiles( |
||||
(request) => |
||||
`SELECT
|
||||
fil.file_uuid, |
||||
fil.file_name, |
||||
fil.file_size, |
||||
fil.file_type, |
||||
fil.file_md5sum, |
||||
fil_loc.file_location_uuid, |
||||
fil_loc.file_location_active, |
||||
anv.anvil_uuid, |
||||
anv.anvil_name, |
||||
anv.anvil_description |
||||
FROM files AS fil |
||||
JOIN file_locations AS fil_loc |
||||
ON fil.file_uuid = fil_loc.file_location_file_uuid |
||||
JOIN anvils AS anv |
||||
ON fil_loc.file_location_anvil_uuid = anv.anvil_uuid |
||||
WHERE fil.file_uuid = '${request.params.fileUUID}' |
||||
AND fil.file_type != 'DELETED';`,
|
||||
); |
||||
|
||||
module.exports = getFileDetail; |
@ -1,13 +0,0 @@ |
||||
const buildGetFiles = require('./buildGetFiles'); |
||||
|
||||
const getFilesOverview = buildGetFiles(` |
||||
SELECT |
||||
file_uuid, |
||||
file_name, |
||||
file_size, |
||||
file_type, |
||||
file_md5sum |
||||
FROM files |
||||
WHERE file_type != 'DELETED';`);
|
||||
|
||||
module.exports = getFilesOverview; |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,8 @@ |
||||
{ |
||||
"extends": ["../.eslintrc.json", "plugin:@typescript-eslint/recommended"], |
||||
"parser": "@typescript-eslint/parser", |
||||
"parserOptions": { |
||||
"project": "./tsconfig.json" |
||||
}, |
||||
"plugins": ["@typescript-eslint"] |
||||
} |
@ -0,0 +1,18 @@ |
||||
import cors from 'cors'; |
||||
import express from 'express'; |
||||
import path from 'path'; |
||||
|
||||
import API_ROOT_PATH from './lib/consts/API_ROOT_PATH'; |
||||
|
||||
import routes from './routes'; |
||||
|
||||
const app = express(); |
||||
|
||||
app.use(express.json()); |
||||
app.use(cors()); |
||||
|
||||
Object.entries(routes).forEach(([route, router]) => { |
||||
app.use(path.join(API_ROOT_PATH, route), router); |
||||
}); |
||||
|
||||
export default app; |
@ -1,6 +1,6 @@ |
||||
const app = require('./app'); |
||||
import app from './app'; |
||||
|
||||
const SERVER_PORT = require('./lib/consts/SERVER_PORT'); |
||||
import SERVER_PORT from './lib/consts/SERVER_PORT'; |
||||
|
||||
app.listen(SERVER_PORT, () => { |
||||
console.log(`Listening on localhost:${SERVER_PORT}.`); |
@ -0,0 +1,207 @@ |
||||
import { spawnSync, SpawnSyncOptions } from 'child_process'; |
||||
|
||||
import SERVER_PATHS from './consts/SERVER_PATHS'; |
||||
|
||||
import { stderr as sherr, stdout as shout } from './shell'; |
||||
|
||||
const formatQuery = (query: string) => query.replace(/\s+/g, ' '); |
||||
|
||||
const execAnvilAccessModule = ( |
||||
args: string[], |
||||
options: SpawnSyncOptions = { |
||||
encoding: 'utf-8', |
||||
timeout: 10000, |
||||
}, |
||||
) => { |
||||
const { error, stdout, stderr } = spawnSync( |
||||
SERVER_PATHS.usr.sbin['anvil-access-module'].self, |
||||
args, |
||||
options, |
||||
); |
||||
|
||||
if (error) { |
||||
throw error; |
||||
} |
||||
|
||||
if (stderr.length > 0) { |
||||
throw new Error(stderr.toString()); |
||||
} |
||||
|
||||
let output; |
||||
|
||||
try { |
||||
output = JSON.parse(stdout.toString()); |
||||
} catch (stdoutParseError) { |
||||
output = stdout; |
||||
|
||||
sherr( |
||||
`Failed to parse anvil-access-module output [${output}]; CAUSE: [${stdoutParseError}]`, |
||||
); |
||||
} |
||||
|
||||
return { |
||||
stdout: output, |
||||
}; |
||||
}; |
||||
|
||||
const execModuleSubroutine = ( |
||||
subName: string, |
||||
{ |
||||
spawnSyncOptions, |
||||
subModuleName, |
||||
subParams, |
||||
}: ExecModuleSubroutineOptions = {}, |
||||
) => { |
||||
const args = ['--sub', subName]; |
||||
|
||||
// Defaults to "Database" in anvil-access-module.
|
||||
if (subModuleName) { |
||||
args.push('--sub-module', subModuleName); |
||||
} |
||||
|
||||
if (subParams) { |
||||
args.push('--sub-params', JSON.stringify(subParams)); |
||||
} |
||||
|
||||
shout( |
||||
`...${subModuleName}->${subName} with params: ${JSON.stringify( |
||||
subParams, |
||||
null, |
||||
2, |
||||
)}`,
|
||||
); |
||||
|
||||
const { stdout } = execAnvilAccessModule(args, spawnSyncOptions); |
||||
|
||||
return { |
||||
stdout: stdout['sub_results'], |
||||
}; |
||||
}; |
||||
|
||||
const dbInsertOrUpdateJob = ( |
||||
{ job_progress = 0, line = 0, ...rest }: DBJobParams, |
||||
{ spawnSyncOptions }: DBInsertOrUpdateJobOptions = {}, |
||||
) => |
||||
execModuleSubroutine('insert_or_update_jobs', { |
||||
spawnSyncOptions, |
||||
subParams: { job_progress, line, ...rest }, |
||||
}).stdout; |
||||
|
||||
const dbInsertOrUpdateVariable: DBInsertOrUpdateVariableFunction = ( |
||||
subParams, |
||||
{ spawnSyncOptions } = {}, |
||||
) => |
||||
execModuleSubroutine('insert_or_update_variables', { |
||||
spawnSyncOptions, |
||||
subParams, |
||||
}).stdout; |
||||
|
||||
const dbJobAnvilSyncShared = ( |
||||
jobName: string, |
||||
jobData: string, |
||||
jobTitle: string, |
||||
jobDescription: string, |
||||
{ jobHostUUID }: DBJobAnvilSyncSharedOptions = { jobHostUUID: undefined }, |
||||
) => { |
||||
const subParams: DBJobParams = { |
||||
file: __filename, |
||||
job_command: SERVER_PATHS.usr.sbin['anvil-sync-shared'].self, |
||||
job_data: jobData, |
||||
job_name: `storage::${jobName}`, |
||||
job_title: `job_${jobTitle}`, |
||||
job_description: `job_${jobDescription}`, |
||||
}; |
||||
|
||||
if (jobHostUUID) { |
||||
subParams.job_host_uuid = jobHostUUID; |
||||
} |
||||
|
||||
return dbInsertOrUpdateJob(subParams); |
||||
}; |
||||
|
||||
const dbQuery = (query: string, options?: SpawnSyncOptions) => { |
||||
shout(formatQuery(query)); |
||||
|
||||
return execAnvilAccessModule(['--query', query], options); |
||||
}; |
||||
|
||||
const dbSubRefreshTimestamp = () => |
||||
execModuleSubroutine('refresh_timestamp').stdout; |
||||
|
||||
const dbWrite = (query: string, options?: SpawnSyncOptions) => { |
||||
shout(formatQuery(query)); |
||||
|
||||
return execAnvilAccessModule(['--query', query, '--mode', 'write'], options); |
||||
}; |
||||
|
||||
const getAnvilData = ( |
||||
dataStruct: AnvilDataStruct, |
||||
{ predata, ...spawnSyncOptions }: GetAnvilDataOptions = {}, |
||||
) => |
||||
execAnvilAccessModule( |
||||
[ |
||||
'--predata', |
||||
JSON.stringify(predata), |
||||
'--data', |
||||
JSON.stringify(dataStruct), |
||||
], |
||||
spawnSyncOptions, |
||||
).stdout; |
||||
|
||||
const getLocalHostUUID = () => { |
||||
let result: string; |
||||
|
||||
try { |
||||
result = execModuleSubroutine('host_uuid', { |
||||
subModuleName: 'Get', |
||||
}).stdout; |
||||
} catch (subError) { |
||||
throw new Error(`Failed to get localhost UUID; CAUSE: ${subError}`); |
||||
} |
||||
|
||||
shout(`localHostUUID=[${result}]`); |
||||
|
||||
return result; |
||||
}; |
||||
|
||||
const getPeerData: GetPeerDataFunction = ( |
||||
target, |
||||
{ password, port, ...restOptions } = {}, |
||||
) => { |
||||
const [ |
||||
rawIsConnected, |
||||
{ |
||||
host_name: hostName, |
||||
host_os: hostOS, |
||||
host_uuid: hostUUID, |
||||
internet: rawIsInetConnected, |
||||
os_registered: rawIsOSRegistered, |
||||
}, |
||||
] = execModuleSubroutine('get_peer_data', { |
||||
subModuleName: 'Striker', |
||||
subParams: { password, port, target }, |
||||
...restOptions, |
||||
}).stdout as [connected: string, data: PeerDataHash]; |
||||
|
||||
return { |
||||
hostName, |
||||
hostOS, |
||||
hostUUID, |
||||
isConnected: rawIsConnected === '1', |
||||
isInetConnected: rawIsInetConnected === '1', |
||||
isOSRegistered: rawIsOSRegistered === 'yes', |
||||
}; |
||||
}; |
||||
|
||||
export { |
||||
dbInsertOrUpdateJob as job, |
||||
dbInsertOrUpdateVariable as variable, |
||||
dbJobAnvilSyncShared, |
||||
dbQuery, |
||||
dbSubRefreshTimestamp, |
||||
dbWrite, |
||||
getAnvilData, |
||||
getLocalHostUUID, |
||||
getPeerData, |
||||
execModuleSubroutine as sub, |
||||
}; |
@ -0,0 +1,38 @@ |
||||
import call from './call'; |
||||
import join from './join'; |
||||
import { sanitize } from './sanitize'; |
||||
|
||||
const buildIDCondition = ( |
||||
keys: Parameters<JoinFunction>[0], |
||||
conditionPrefix: string, |
||||
{ |
||||
onFallback, |
||||
beforeReturn = (result) => |
||||
result |
||||
? `${conditionPrefix} IN (${result})` |
||||
: call(onFallback, { notCallableReturn: '' }), |
||||
}: Pick<JoinOptions, 'beforeReturn'> & { onFallback?: () => string } = {}, |
||||
) => |
||||
join(keys, { |
||||
beforeReturn, |
||||
elementWrapper: "'", |
||||
separator: ', ', |
||||
}) as string; |
||||
|
||||
export const buildUnknownIDCondition = ( |
||||
keys: unknown, |
||||
conditionPrefix: string, |
||||
{ onFallback }: { onFallback?: () => string }, |
||||
): { after: string; before: string[] } => { |
||||
const before = sanitize(keys, 'string[]', { |
||||
modifierType: 'sql', |
||||
}); |
||||
const after = buildIDCondition(before, conditionPrefix, { onFallback }); |
||||
|
||||
return { after, before }; |
||||
}; |
||||
|
||||
export const buildKnownIDCondition = ( |
||||
keys: string[] | '*' = '*', |
||||
conditionPrefix: string, |
||||
) => (keys[0] === '*' ? '' : buildIDCondition(keys, conditionPrefix)); |
@ -0,0 +1,14 @@ |
||||
type QueryField = string; |
||||
|
||||
export const buildQueryResultModifier = |
||||
<T>(mod: (output: QueryField[][]) => T): QueryResultModifierFunction => |
||||
(output) => |
||||
output instanceof Array ? mod(output) : output; |
||||
|
||||
export const buildQueryResultReducer = <T>( |
||||
reduce: (previous: T, row: QueryField[]) => T, |
||||
initialValue: T, |
||||
) => |
||||
buildQueryResultModifier<T>((output) => |
||||
output.reduce<T>(reduce, initialValue), |
||||
); |
@ -0,0 +1,7 @@ |
||||
const call = <T = unknown>( |
||||
toCall: unknown, |
||||
{ parameters = [], notCallableReturn }: CallOptions = {}, |
||||
): T => |
||||
typeof toCall === 'function' ? toCall(...parameters) : notCallableReturn; |
||||
|
||||
export default call; |
@ -0,0 +1,4 @@ |
||||
export const cap = ( |
||||
value: string, |
||||
{ locales }: { locales?: string | string[] } = {}, |
||||
) => `${value[0].toLocaleUpperCase(locales)}${value.slice(1)}`; |
@ -0,0 +1,3 @@ |
||||
const API_ROOT_PATH = '/api'; |
||||
|
||||
export default API_ROOT_PATH; |
@ -0,0 +1 @@ |
||||
export const HOST_KEY_CHANGED_PREFIX = 'host_key_changed::'; |
@ -0,0 +1 @@ |
||||
export const LOCAL = 'local'; |
@ -0,0 +1,4 @@ |
||||
// Unit: bytes
|
||||
const NODE_AND_DR_RESERVED_MEMORY_SIZE = 8589934592; |
||||
|
||||
export default NODE_AND_DR_RESERVED_MEMORY_SIZE; |
@ -0,0 +1,26 @@ |
||||
import { execSync } from 'child_process'; |
||||
|
||||
import SERVER_PATHS from './SERVER_PATHS'; |
||||
|
||||
type OSKeyMapToName = Record<string, string>; |
||||
|
||||
const osList: string[] = execSync( |
||||
`${SERVER_PATHS.usr.sbin['striker-parse-os-list'].self} | ${SERVER_PATHS.usr.bin['sed'].self} -E 's/^.*name="os_list_([^"]+).*CDATA[[]([^]]+).*$/\\1,\\2/'`, |
||||
{ |
||||
encoding: 'utf-8', |
||||
timeout: 10000, |
||||
}, |
||||
).split('\n'); |
||||
|
||||
osList.pop(); |
||||
|
||||
const osKeyMapToName: OSKeyMapToName = osList.reduce((map, csv) => { |
||||
const [osKey, osName] = csv.split(',', 2); |
||||
|
||||
map[osKey] = osName; |
||||
|
||||
return map; |
||||
}, {} as OSKeyMapToName); |
||||
|
||||
export const OS_LIST: Readonly<string[]> = osList; |
||||
export const OS_LIST_MAP: Readonly<OSKeyMapToName> = osKeyMapToName; |
@ -0,0 +1,23 @@ |
||||
const hex = '[0-9a-f]'; |
||||
const octet = '(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]|)[0-9])'; |
||||
const alphanumeric = '[a-z0-9]'; |
||||
const alphanumericDash = '[a-z0-9-]'; |
||||
const ipv4 = `(?:${octet}[.]){3}${octet}`; |
||||
|
||||
export const REP_DOMAIN = new RegExp( |
||||
`^(?:${alphanumeric}(?:${alphanumericDash}{0,61}${alphanumeric})?[.])+${alphanumeric}${alphanumericDash}{0,61}${alphanumeric}$`, |
||||
); |
||||
|
||||
export const REP_INTEGER = /^\d+$/; |
||||
|
||||
export const REP_IPV4 = new RegExp(`^${ipv4}$`); |
||||
|
||||
export const REP_IPV4_CSV = new RegExp(`(?:${ipv4},)*${ipv4}`); |
||||
|
||||
// Peaceful string is temporarily defined as a string without single-quote, double-quote, slash (/), backslash (\\), angle brackets (< >), and curly brackets ({ }).
|
||||
export const REP_PEACEFUL_STRING = /^[^'"/\\><}{]+$/; |
||||
|
||||
export const REP_UUID = new RegExp( |
||||
`^${hex}{8}-${hex}{4}-[1-5]${hex}{3}-[89ab]${hex}{3}-${hex}{12}$`, |
||||
'i', |
||||
); |
@ -0,0 +1,54 @@ |
||||
import path from 'path'; |
||||
|
||||
const EMPTY_SERVER_PATHS: ServerPath = { |
||||
mnt: { |
||||
shared: { |
||||
incoming: {}, |
||||
}, |
||||
}, |
||||
tmp: {}, |
||||
usr: { |
||||
bin: { |
||||
date: {}, |
||||
mkfifo: {}, |
||||
psql: {}, |
||||
rm: {}, |
||||
sed: {}, |
||||
}, |
||||
sbin: { |
||||
'anvil-access-module': {}, |
||||
'anvil-configure-host': {}, |
||||
'anvil-get-server-screenshot': {}, |
||||
'anvil-manage-keys': {}, |
||||
'anvil-manage-power': {}, |
||||
'anvil-provision-server': {}, |
||||
'anvil-sync-shared': {}, |
||||
'anvil-update-system': {}, |
||||
'striker-initialize-host': {}, |
||||
'striker-manage-install-target': {}, |
||||
'striker-manage-peers': {}, |
||||
'striker-parse-os-list': {}, |
||||
}, |
||||
}, |
||||
}; |
||||
|
||||
const generatePaths = ( |
||||
currentObject: ServerPath, |
||||
parents = path.parse(process.cwd()).root, |
||||
) => { |
||||
Object.keys(currentObject).forEach((pathKey) => { |
||||
if (pathKey !== 'self') { |
||||
const currentPath = path.join(parents, pathKey); |
||||
|
||||
currentObject[pathKey].self = currentPath; |
||||
|
||||
generatePaths(currentObject[pathKey], currentPath); |
||||
} |
||||
}); |
||||
|
||||
return currentObject as ReadonlyServerPath; |
||||
}; |
||||
|
||||
const SERVER_PATHS = generatePaths(EMPTY_SERVER_PATHS); |
||||
|
||||
export default SERVER_PATHS; |
@ -1,3 +1,3 @@ |
||||
const SERVER_PORT = process.env.SERVER_PORT ?? 8080; |
||||
|
||||
module.exports = SERVER_PORT; |
||||
export default SERVER_PORT; |
@ -0,0 +1,13 @@ |
||||
import { LOCAL } from './consts/LOCAL'; |
||||
|
||||
import { getLocalHostUUID } from './accessModule'; |
||||
|
||||
export const toHostUUID = ( |
||||
hostUUID: string, |
||||
localHostUUID: string = getLocalHostUUID(), |
||||
) => (hostUUID === LOCAL ? localHostUUID : hostUUID); |
||||
|
||||
export const toLocal = ( |
||||
hostUUID: string, |
||||
localHostUUID: string = getLocalHostUUID(), |
||||
) => (hostUUID === localHostUUID ? LOCAL : hostUUID); |
@ -0,0 +1,2 @@ |
||||
export const getShortHostName = (hostName: string) => |
||||
hostName.replace(/[.].*$/, ''); |
@ -0,0 +1,27 @@ |
||||
import call from './call'; |
||||
|
||||
const join: JoinFunction = ( |
||||
elements, |
||||
{ beforeReturn, elementWrapper = '', onEach, separator = '' } = {}, |
||||
) => { |
||||
const joinSeparator = `${elementWrapper}${separator}${elementWrapper}`; |
||||
|
||||
const toReturn = |
||||
elements instanceof Array && elements.length > 0 |
||||
? `${elementWrapper}${elements.slice(1).reduce<string>( |
||||
(previous, element) => |
||||
`${previous}${joinSeparator}${call<string>(onEach, { |
||||
notCallableReturn: element, |
||||
parameters: [element], |
||||
})}`,
|
||||
elements[0], |
||||
)}${elementWrapper}` |
||||
: undefined; |
||||
|
||||
return call<string | undefined>(beforeReturn, { |
||||
notCallableReturn: toReturn, |
||||
parameters: [toReturn], |
||||
}); |
||||
}; |
||||
|
||||
export default join; |
@ -0,0 +1,5 @@ |
||||
export const match = ( |
||||
value: string, |
||||
regexp: string | RegExp, |
||||
{ fallbackResult = [] }: { fallbackResult?: string[] } = {}, |
||||
) => value.match(regexp) ?? fallbackResult; |
@ -0,0 +1,323 @@ |
||||
import NODE_AND_DR_RESERVED_MEMORY_SIZE from '../../consts/NODE_AND_DR_RESERVED_MEMORY_SIZE'; |
||||
import { OS_LIST } from '../../consts/OS_LIST'; |
||||
|
||||
import join from '../../join'; |
||||
|
||||
const buildQueryAnvilDetail = ({ |
||||
anvilUUIDs = ['*'], |
||||
isForProvisionServer, |
||||
}: { |
||||
anvilUUIDs?: string[] | '*'; |
||||
isForProvisionServer?: boolean; |
||||
}) => { |
||||
const condAnvilsUUID = ['all', '*'].includes(anvilUUIDs[0]) |
||||
? '' |
||||
: join(anvilUUIDs, { |
||||
beforeReturn: (toReturn) => |
||||
toReturn ? `WHERE anv.anvil_uuid IN (${toReturn})` : '', |
||||
elementWrapper: "'", |
||||
separator: ', ', |
||||
}); |
||||
|
||||
console.log(`condAnvilsUUID=[${condAnvilsUUID}]`); |
||||
|
||||
const buildHostQuery = ({ |
||||
isSummary = false, |
||||
}: { isSummary?: boolean } = {}) => { |
||||
let fieldsToSelect = ` |
||||
host_uuid, |
||||
host_name, |
||||
scan_hardware_cpu_cores, |
||||
scan_hardware_ram_total`;
|
||||
let groupByPhrase = ''; |
||||
|
||||
if (isSummary) { |
||||
fieldsToSelect = ` |
||||
MIN(scan_hardware_cpu_cores) AS anvil_total_cpu_cores, |
||||
MIN(scan_hardware_ram_total) AS anvil_total_memory`;
|
||||
|
||||
groupByPhrase = 'GROUP BY anvil_uuid'; |
||||
} |
||||
|
||||
return ` |
||||
SELECT |
||||
anvil_uuid, |
||||
${fieldsToSelect} |
||||
FROM anvils AS anv |
||||
JOIN hosts AS hos |
||||
ON host_uuid IN ( |
||||
anvil_node1_host_uuid, |
||||
anvil_node2_host_uuid, |
||||
anvil_dr1_host_uuid |
||||
) |
||||
JOIN scan_hardware AS sca_har |
||||
ON host_uuid = scan_hardware_host_uuid |
||||
${groupByPhrase}`;
|
||||
}; |
||||
|
||||
const buildServerQuery = ({ |
||||
isSummary = false, |
||||
}: { isSummary?: boolean } = {}) => { |
||||
let fieldsToSelect = ` |
||||
server_uuid, |
||||
server_name, |
||||
server_cpu_cores, |
||||
server_memory`;
|
||||
let groupByPhrase = ''; |
||||
|
||||
if (isSummary) { |
||||
fieldsToSelect = ` |
||||
SUM(server_cpu_cores) AS anvil_total_allocated_cpu_cores, |
||||
SUM(server_memory) AS anvil_total_allocated_memory`;
|
||||
|
||||
groupByPhrase = 'GROUP BY server_anvil_uuid'; |
||||
} |
||||
|
||||
return ` |
||||
SELECT |
||||
server_anvil_uuid, |
||||
${fieldsToSelect} |
||||
FROM servers AS ser |
||||
JOIN ( |
||||
SELECT |
||||
server_definition_server_uuid, |
||||
server_cpu_cores, |
||||
CASE server_memory_unit |
||||
WHEN 'KiB' THEN server_memory_value * 1024 |
||||
ELSE server_memory_value |
||||
END AS server_memory |
||||
FROM ( |
||||
SELECT |
||||
server_definition_server_uuid, |
||||
CAST( |
||||
SUBSTRING( |
||||
server_definition_xml, '%cores=''#"[0-9]+#"''%', '#' |
||||
) AS INTEGER |
||||
) AS server_cpu_cores, |
||||
CAST( |
||||
SUBSTRING( |
||||
server_definition_xml, '%memory%>#"[0-9]+#"</memory%', '#' |
||||
) AS BIGINT |
||||
) AS server_memory_value, |
||||
SUBSTRING( |
||||
server_definition_xml, '%memory%unit=''#"[A-Za-z]+#"''%', '#' |
||||
) AS server_memory_unit |
||||
FROM server_definitions AS ser_def |
||||
) AS ser_def_memory_converted |
||||
) AS pos_ser_def |
||||
ON server_uuid = server_definition_server_uuid |
||||
${groupByPhrase}`;
|
||||
}; |
||||
|
||||
const buildStorageGroupQuery = () => ` |
||||
SELECT |
||||
storage_group_anvil_uuid, |
||||
storage_group_uuid, |
||||
storage_group_name, |
||||
MIN(scan_lvm_vg_size) AS storage_group_size, |
||||
MIN(scan_lvm_vg_free) AS storage_group_free |
||||
FROM storage_groups AS sto_gro |
||||
JOIN storage_group_members AS sto_gro_mem |
||||
ON storage_group_uuid = storage_group_member_storage_group_uuid |
||||
JOIN scan_lvm_vgs AS sca_lvm_vgs |
||||
ON storage_group_member_vg_uuid = scan_lvm_vg_internal_uuid |
||||
GROUP BY |
||||
storage_group_anvil_uuid, |
||||
storage_group_uuid, |
||||
storage_group_name`;
|
||||
|
||||
const buildFileQuery = () => ` |
||||
SELECT |
||||
file_location_anvil_uuid, |
||||
file_uuid, |
||||
file_name |
||||
FROM file_locations as fil_loc |
||||
JOIN files as fil |
||||
ON file_location_file_uuid = file_uuid |
||||
WHERE |
||||
file_type = 'iso' |
||||
AND file_location_active = 't'`;
|
||||
|
||||
const buildQueryForProvisionServer = () => ` |
||||
SELECT |
||||
anv.anvil_uuid, |
||||
anv.anvil_name, |
||||
anv.anvil_description, |
||||
host_list.host_uuid, |
||||
host_list.host_name, |
||||
host_list.scan_hardware_cpu_cores, |
||||
host_list.scan_hardware_ram_total, |
||||
host_summary.anvil_total_cpu_cores, |
||||
host_summary.anvil_total_memory, |
||||
server_list.server_uuid, |
||||
server_list.server_name, |
||||
server_list.server_cpu_cores, |
||||
server_list.server_memory, |
||||
server_summary.anvil_total_allocated_cpu_cores, |
||||
server_summary.anvil_total_allocated_memory, |
||||
(host_summary.anvil_total_cpu_cores |
||||
- server_summary.anvil_total_allocated_cpu_cores |
||||
) AS anvil_total_available_cpu_cores, |
||||
(host_summary.anvil_total_memory |
||||
- server_summary.anvil_total_allocated_memory |
||||
- ${NODE_AND_DR_RESERVED_MEMORY_SIZE} |
||||
) AS anvil_total_available_memory, |
||||
storage_group_list.storage_group_uuid, |
||||
storage_group_list.storage_group_name, |
||||
storage_group_list.storage_group_size, |
||||
storage_group_list.storage_group_free, |
||||
file_list.file_uuid, |
||||
file_list.file_name |
||||
FROM anvils AS anv |
||||
JOIN (${buildHostQuery()}) AS host_list |
||||
ON anv.anvil_uuid = host_list.anvil_uuid |
||||
JOIN (${buildHostQuery({ isSummary: true })}) AS host_summary |
||||
ON anv.anvil_uuid = host_summary.anvil_uuid |
||||
LEFT JOIN (${buildServerQuery()}) AS server_list |
||||
ON anv.anvil_uuid = server_list.server_anvil_uuid |
||||
LEFT JOIN (${buildServerQuery({ isSummary: true })}) AS server_summary |
||||
ON anv.anvil_uuid = server_summary.server_anvil_uuid |
||||
LEFT JOIN (${buildStorageGroupQuery()}) AS storage_group_list |
||||
ON anv.anvil_uuid = storage_group_list.storage_group_anvil_uuid |
||||
LEFT JOIN (${buildFileQuery()}) AS file_list |
||||
ON anv.anvil_uuid = file_list.file_location_anvil_uuid |
||||
;`;
|
||||
|
||||
let query = ` |
||||
SELECT |
||||
* |
||||
FROM anvils AS anv |
||||
${condAnvilsUUID} |
||||
;`;
|
||||
let afterQueryReturn = undefined; |
||||
|
||||
if (isForProvisionServer) { |
||||
query = buildQueryForProvisionServer(); |
||||
|
||||
afterQueryReturn = (queryStdout: unknown) => { |
||||
let results = queryStdout; |
||||
|
||||
if (queryStdout instanceof Array) { |
||||
let rowStage: AnvilDetailForProvisionServer | undefined; |
||||
|
||||
const anvils = queryStdout.reduce<AnvilDetailForProvisionServer[]>( |
||||
( |
||||
reducedRows, |
||||
[ |
||||
anvilUUID, |
||||
anvilName, |
||||
anvilDescription, |
||||
hostUUID, |
||||
hostName, |
||||
hostCPUCores, |
||||
hostMemory, |
||||
anvilTotalCPUCores, |
||||
anvilTotalMemory, |
||||
serverUUID, |
||||
serverName, |
||||
serverCPUCores, |
||||
serverMemory, |
||||
anvilTotalAllocatedCPUCores, |
||||
anvilTotalAllocatedMemory, |
||||
anvilTotalAvailableCPUCores, |
||||
anvilTotalAvailableMemory, |
||||
storageGroupUUID, |
||||
storageGroupName, |
||||
storageGroupSize, |
||||
storageGroupFree, |
||||
fileUUID, |
||||
fileName, |
||||
], |
||||
) => { |
||||
if (!rowStage || anvilUUID !== rowStage.anvilUUID) { |
||||
rowStage = { |
||||
anvilUUID, |
||||
anvilName, |
||||
anvilDescription, |
||||
anvilTotalCPUCores: parseInt(anvilTotalCPUCores), |
||||
anvilTotalMemory: String(anvilTotalMemory), |
||||
anvilTotalAllocatedCPUCores: parseInt( |
||||
anvilTotalAllocatedCPUCores, |
||||
), |
||||
anvilTotalAllocatedMemory: String(anvilTotalAllocatedMemory), |
||||
anvilTotalAvailableCPUCores: parseInt( |
||||
anvilTotalAvailableCPUCores, |
||||
), |
||||
anvilTotalAvailableMemory: String(anvilTotalAvailableMemory), |
||||
hosts: [], |
||||
servers: [], |
||||
storageGroups: [], |
||||
files: [], |
||||
}; |
||||
|
||||
reducedRows.push(rowStage); |
||||
} |
||||
|
||||
if ( |
||||
!rowStage.hosts.find(({ hostUUID: added }) => added === hostUUID) |
||||
) { |
||||
rowStage.hosts.push({ |
||||
hostUUID, |
||||
hostName, |
||||
hostCPUCores: parseInt(hostCPUCores), |
||||
hostMemory: String(hostMemory), |
||||
}); |
||||
} |
||||
|
||||
if ( |
||||
!rowStage.servers.find( |
||||
({ serverUUID: added }) => added === serverUUID, |
||||
) |
||||
) { |
||||
rowStage.servers.push({ |
||||
serverUUID, |
||||
serverName, |
||||
serverCPUCores: parseInt(serverCPUCores), |
||||
serverMemory: String(serverMemory), |
||||
}); |
||||
} |
||||
|
||||
if ( |
||||
!rowStage.storageGroups.find( |
||||
({ storageGroupUUID: added }) => added === storageGroupUUID, |
||||
) |
||||
) { |
||||
rowStage.storageGroups.push({ |
||||
storageGroupUUID, |
||||
storageGroupName, |
||||
storageGroupSize: String(storageGroupSize), |
||||
storageGroupFree: String(storageGroupFree), |
||||
}); |
||||
} |
||||
|
||||
if ( |
||||
!rowStage.files.find(({ fileUUID: added }) => added === fileUUID) |
||||
) { |
||||
rowStage.files.push({ |
||||
fileUUID, |
||||
fileName, |
||||
}); |
||||
} |
||||
|
||||
return reducedRows; |
||||
}, |
||||
[], |
||||
); |
||||
|
||||
results = { |
||||
anvils, |
||||
osList: OS_LIST, |
||||
}; |
||||
} |
||||
|
||||
return results; |
||||
}; |
||||
} |
||||
|
||||
return { |
||||
query, |
||||
afterQueryReturn, |
||||
}; |
||||
}; |
||||
|
||||
export default buildQueryAnvilDetail; |
@ -0,0 +1,81 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import buildQueryAnvilDetail from './buildQueryAnvilDetail'; |
||||
import { sanitize } from '../../sanitize'; |
||||
|
||||
const getAnvil: RequestHandler = buildGetRequestHandler( |
||||
(request, buildQueryOptions) => { |
||||
const { anvilUUIDs, isForProvisionServer } = request.query; |
||||
|
||||
let query = ` |
||||
SELECT |
||||
anv.anvil_name, |
||||
anv.anvil_uuid, |
||||
hos.host_name, |
||||
hos.host_uuid |
||||
FROM anvils AS anv |
||||
JOIN hosts AS hos |
||||
ON hos.host_uuid IN ( |
||||
anv.anvil_node1_host_uuid, |
||||
anv.anvil_node2_host_uuid, |
||||
anv.anvil_dr1_host_uuid |
||||
) |
||||
ORDER BY anv.anvil_uuid;`;
|
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = (queryStdout) => { |
||||
let results = queryStdout; |
||||
|
||||
if (queryStdout instanceof Array) { |
||||
let rowStage: AnvilOverview | undefined; |
||||
|
||||
results = queryStdout.reduce<AnvilOverview[]>( |
||||
(reducedRows, [anvilName, anvilUUID, hostName, hostUUID]) => { |
||||
if (!rowStage || anvilUUID !== rowStage.anvilUUID) { |
||||
{ |
||||
rowStage = { |
||||
anvilName, |
||||
anvilUUID, |
||||
hosts: [], |
||||
}; |
||||
|
||||
reducedRows.push(rowStage); |
||||
} |
||||
} |
||||
|
||||
rowStage.hosts.push({ hostName, hostUUID }); |
||||
|
||||
return reducedRows; |
||||
}, |
||||
[], |
||||
); |
||||
} |
||||
|
||||
return results; |
||||
}; |
||||
} |
||||
|
||||
if (anvilUUIDs) { |
||||
const { |
||||
query: anvilDetailQuery, |
||||
afterQueryReturn: anvilDetailAfterQueryReturn, |
||||
} = buildQueryAnvilDetail({ |
||||
anvilUUIDs: sanitize(anvilUUIDs, 'string[]', { |
||||
modifierType: 'sql', |
||||
}), |
||||
isForProvisionServer: sanitize(isForProvisionServer, 'boolean'), |
||||
}); |
||||
|
||||
query = anvilDetailQuery; |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = anvilDetailAfterQueryReturn; |
||||
} |
||||
} |
||||
|
||||
return query; |
||||
}, |
||||
); |
||||
|
||||
export default getAnvil; |
@ -0,0 +1,34 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { sanitize } from '../sanitize'; |
||||
import { stderr, stdout } from '../shell'; |
||||
|
||||
export const buildBranchRequestHandler: (map: { |
||||
[handler: string]: RequestHandler | undefined; |
||||
}) => RequestHandler = |
||||
(map) => |
||||
(...args) => { |
||||
const [ |
||||
{ |
||||
query: { handler: rawHandler }, |
||||
}, |
||||
response, |
||||
] = args; |
||||
|
||||
const handlerKey = sanitize(rawHandler, 'string'); |
||||
|
||||
stdout(`Create host handler: ${handlerKey}`); |
||||
|
||||
// Ensure each handler sends a response at the end of any branch.
|
||||
const handler = map[handlerKey]; |
||||
|
||||
if (handler) { |
||||
handler(...args); |
||||
} else { |
||||
stderr(`Handler is not registered; got [${handlerKey}]`); |
||||
|
||||
response.status(400).send(); |
||||
|
||||
return; |
||||
} |
||||
}; |
@ -0,0 +1,64 @@ |
||||
import { Request, Response } from 'express'; |
||||
|
||||
import { dbQuery } from '../accessModule'; |
||||
import call from '../call'; |
||||
|
||||
const buildGetRequestHandler = |
||||
( |
||||
query: string | BuildQueryFunction, |
||||
{ beforeRespond }: BuildGetRequestHandlerOptions = {}, |
||||
) => |
||||
(request: Request, response: Response) => { |
||||
console.log('Calling CLI script to get data.'); |
||||
|
||||
const buildQueryOptions: BuildQueryOptions = {}; |
||||
|
||||
let queryStdout; |
||||
|
||||
try { |
||||
({ stdout: queryStdout } = dbQuery( |
||||
call<string>(query, { |
||||
parameters: [request, buildQueryOptions], |
||||
notCallableReturn: query, |
||||
}), |
||||
)); |
||||
} catch (queryError) { |
||||
console.log(`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, |
||||
)}`,
|
||||
); |
||||
|
||||
const { afterQueryReturn } = buildQueryOptions; |
||||
|
||||
queryStdout = call(afterQueryReturn, { |
||||
parameters: [queryStdout], |
||||
notCallableReturn: queryStdout, |
||||
}); |
||||
|
||||
queryStdout = call(beforeRespond, { |
||||
parameters: [queryStdout], |
||||
notCallableReturn: queryStdout, |
||||
}); |
||||
|
||||
console.log( |
||||
`Query stdout post-hooks (type=[${typeof queryStdout}]): ${JSON.stringify( |
||||
queryStdout, |
||||
null, |
||||
2, |
||||
)}`,
|
||||
); |
||||
|
||||
response.json(queryStdout); |
||||
}; |
||||
|
||||
export default buildGetRequestHandler; |
@ -0,0 +1,53 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { job } from '../../accessModule'; |
||||
import { stderr } from '../../shell'; |
||||
|
||||
type DistinctDBJobParams = Omit< |
||||
DBJobParams, |
||||
'file' | 'line' | 'job_data' | 'job_progress' |
||||
>; |
||||
|
||||
const MANAGE_HOST_POWER_JOB_PARAMS: { |
||||
poweroff: DistinctDBJobParams; |
||||
reboot: DistinctDBJobParams; |
||||
} = { |
||||
poweroff: { |
||||
job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --poweroff -y`, |
||||
job_name: 'poweroff::system', |
||||
job_title: 'job_0010', |
||||
job_description: 'job_0008', |
||||
}, |
||||
reboot: { |
||||
job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --reboot -y`, |
||||
job_name: 'reboot::system', |
||||
job_title: 'job_0009', |
||||
job_description: 'job_0006', |
||||
}, |
||||
}; |
||||
|
||||
export const buildHostPowerHandler: ( |
||||
task?: 'poweroff' | 'reboot', |
||||
) => RequestHandler = |
||||
(task = 'reboot') => |
||||
(request, response) => { |
||||
const subParams: DBJobParams = { |
||||
file: __filename, |
||||
|
||||
...MANAGE_HOST_POWER_JOB_PARAMS[task], |
||||
}; |
||||
|
||||
try { |
||||
job(subParams); |
||||
} catch (subError) { |
||||
stderr(`Failed to ${task} host; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
response.status(204).send(); |
||||
}; |
@ -0,0 +1,88 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { HOST_KEY_CHANGED_PREFIX } from '../../consts/HOST_KEY_CHANGED_PREFIX'; |
||||
|
||||
import { dbQuery, getLocalHostUUID, getPeerData } from '../../accessModule'; |
||||
import { sanitizeSQLParam } from '../../sanitizeSQLParam'; |
||||
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) => { |
||||
const { |
||||
body: { password, port = 22, ipAddress: target }, |
||||
} = request; |
||||
|
||||
let hostName: string; |
||||
let hostOS: string; |
||||
let hostUUID: string; |
||||
let isConnected: boolean; |
||||
let isInetConnected: boolean; |
||||
let isOSRegistered: boolean; |
||||
|
||||
const localHostUUID = getLocalHostUUID(); |
||||
|
||||
try { |
||||
({ |
||||
hostName, |
||||
hostOS, |
||||
hostUUID, |
||||
isConnected, |
||||
isInetConnected, |
||||
isOSRegistered, |
||||
} = getPeerData(target, { password, port })); |
||||
} catch (subError) { |
||||
stderr(`Failed to get peer data; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
let badSSHKeys: DeleteSSHKeyConflictRequestBody | undefined; |
||||
|
||||
if (!isConnected) { |
||||
const rows = dbQuery(` |
||||
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][];
|
||||
|
||||
if (rows.length > 0) { |
||||
badSSHKeys = rows.reduce<DeleteSSHKeyConflictRequestBody>( |
||||
(previous, [, stateUUID]) => { |
||||
previous[localHostUUID].push(stateUUID); |
||||
|
||||
return previous; |
||||
}, |
||||
{ [localHostUUID]: [] }, |
||||
); |
||||
} |
||||
} |
||||
|
||||
response.status(200).send({ |
||||
badSSHKeys, |
||||
hostName, |
||||
hostOS, |
||||
hostUUID, |
||||
isConnected, |
||||
isInetConnected, |
||||
isOSRegistered, |
||||
}); |
||||
}; |
@ -0,0 +1,4 @@ |
||||
export * from './getHostSSH'; |
||||
export * from './poweroffHost'; |
||||
export * from './rebootHost'; |
||||
export * from './updateSystem'; |
@ -0,0 +1,3 @@ |
||||
import { buildHostPowerHandler } from './buildHostPowerHandler'; |
||||
|
||||
export const poweroffHost = buildHostPowerHandler('poweroff'); |
@ -0,0 +1,3 @@ |
||||
import { buildHostPowerHandler } from './buildHostPowerHandler'; |
||||
|
||||
export const rebootHost = buildHostPowerHandler('reboot'); |
@ -0,0 +1,26 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { job } from '../../accessModule'; |
||||
import { stderr } from '../../shell'; |
||||
|
||||
export const updateSystem: RequestHandler = (request, response) => { |
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: SERVER_PATHS.usr.sbin['anvil-update-system'].self, |
||||
job_description: 'job_0004', |
||||
job_name: 'update::system', |
||||
job_title: 'job_0003', |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to initiate system update; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
response.status(204).send(); |
||||
}; |
@ -0,0 +1,40 @@ |
||||
import join from '../../join'; |
||||
|
||||
const buildQueryFileDetail = ({ |
||||
fileUUIDs = ['*'], |
||||
}: { |
||||
fileUUIDs?: string[] | '*'; |
||||
}) => { |
||||
const condFileUUIDs = ['all', '*'].includes(fileUUIDs[0]) |
||||
? '' |
||||
: join(fileUUIDs, { |
||||
beforeReturn: (toReturn) => |
||||
toReturn ? `AND fil.file_uuid IN (${toReturn})` : '', |
||||
elementWrapper: "'", |
||||
separator: ', ', |
||||
}); |
||||
|
||||
console.log(`condFilesUUID=[${condFileUUIDs}]`); |
||||
|
||||
return ` |
||||
SELECT |
||||
fil.file_uuid, |
||||
fil.file_name, |
||||
fil.file_size, |
||||
fil.file_type, |
||||
fil.file_md5sum, |
||||
fil_loc.file_location_uuid, |
||||
fil_loc.file_location_active, |
||||
anv.anvil_uuid, |
||||
anv.anvil_name, |
||||
anv.anvil_description |
||||
FROM files AS fil |
||||
JOIN file_locations AS fil_loc |
||||
ON fil.file_uuid = fil_loc.file_location_file_uuid |
||||
JOIN anvils AS anv |
||||
ON fil_loc.file_location_anvil_uuid = anv.anvil_uuid |
||||
WHERE fil.file_type != 'DELETED' |
||||
${condFileUUIDs};`;
|
||||
}; |
||||
|
||||
export default buildQueryFileDetail; |
@ -0,0 +1,31 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import buildQueryFileDetail from './buildQueryFileDetail'; |
||||
import { sanitize } from '../../sanitize'; |
||||
|
||||
const getFile: RequestHandler = buildGetRequestHandler((request) => { |
||||
const { fileUUIDs } = request.query; |
||||
|
||||
let query = ` |
||||
SELECT |
||||
file_uuid, |
||||
file_name, |
||||
file_size, |
||||
file_type, |
||||
file_md5sum |
||||
FROM files |
||||
WHERE file_type != 'DELETED';`;
|
||||
|
||||
if (fileUUIDs) { |
||||
query = buildQueryFileDetail({ |
||||
fileUUIDs: sanitize(fileUUIDs, 'string[]', { |
||||
modifierType: 'sql', |
||||
}), |
||||
}); |
||||
} |
||||
|
||||
return query; |
||||
}); |
||||
|
||||
export default getFile; |
@ -0,0 +1,12 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import buildQueryFileDetail from './buildQueryFileDetail'; |
||||
import { sanitizeSQLParam } from '../../sanitizeSQLParam'; |
||||
|
||||
const getFileDetail: RequestHandler = buildGetRequestHandler( |
||||
({ params: { fileUUID } }) => |
||||
buildQueryFileDetail({ fileUUIDs: [sanitizeSQLParam(fileUUID)] }), |
||||
); |
||||
|
||||
export default getFileDetail; |
@ -0,0 +1,68 @@ |
||||
import { buildKnownIDCondition } from '../../buildCondition'; |
||||
import { buildQueryResultModifier } from '../../buildQueryResultModifier'; |
||||
import { cap } from '../../cap'; |
||||
import { getShortHostName } from '../../getShortHostName'; |
||||
import { stdout } from '../../shell'; |
||||
|
||||
type ExtractVariableKeyFunction = (parts: string[]) => string; |
||||
|
||||
const MAP_TO_EXTRACTOR: { [prefix: string]: ExtractVariableKeyFunction } = { |
||||
form: ([, part2]) => { |
||||
const [head, ...rest] = part2.split('_'); |
||||
|
||||
return rest.reduce<string>( |
||||
(previous, part) => `${previous}${cap(part)}`, |
||||
head, |
||||
); |
||||
}, |
||||
'install-target': () => 'installTarget', |
||||
}; |
||||
|
||||
export const buildQueryHostDetail: BuildQueryDetailFunction = ({ |
||||
keys: hostUUIDs = '*', |
||||
} = {}) => { |
||||
const condHostUUIDs = buildKnownIDCondition(hostUUIDs, 'AND hos.host_uuid'); |
||||
|
||||
stdout(`condHostUUIDs=[${condHostUUIDs}]`); |
||||
|
||||
const query = ` |
||||
SELECT |
||||
hos.host_name, |
||||
hos.host_uuid, |
||||
var.variable_name, |
||||
var.variable_value |
||||
FROM variables AS var |
||||
JOIN hosts AS hos |
||||
ON var.variable_source_uuid = hos.host_uuid |
||||
WHERE ( |
||||
variable_name LIKE 'form::config_%' |
||||
OR variable_name = 'install-target::enabled' |
||||
) |
||||
${condHostUUIDs};`;
|
||||
|
||||
const afterQueryReturn: QueryResultModifierFunction = |
||||
buildQueryResultModifier((output) => { |
||||
const [hostName, hostUUID] = output[0]; |
||||
const shortHostName = getShortHostName(hostName); |
||||
|
||||
return output.reduce< |
||||
{ hostName: string; hostUUID: string; shortHostName: string } & Record< |
||||
string, |
||||
string |
||||
> |
||||
>( |
||||
(previous, [, , variableName, variableValue]) => { |
||||
const [variablePrefix, ...restVariableParts] = |
||||
variableName.split('::'); |
||||
const key = MAP_TO_EXTRACTOR[variablePrefix](restVariableParts); |
||||
|
||||
previous[key] = variableValue; |
||||
|
||||
return previous; |
||||
}, |
||||
{ hostName, hostUUID, shortHostName }, |
||||
); |
||||
}); |
||||
|
||||
return { query, afterQueryReturn }; |
||||
}; |
@ -0,0 +1,164 @@ |
||||
import assert from 'assert'; |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { |
||||
REP_DOMAIN, |
||||
REP_INTEGER, |
||||
REP_IPV4, |
||||
REP_IPV4_CSV, |
||||
} from '../../consts/REG_EXP_PATTERNS'; |
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { job } from '../../accessModule'; |
||||
|
||||
const fvar = (configStepCount: number, fieldName: string) => |
||||
['form', `config_step${configStepCount}`, fieldName, 'value'].join('::'); |
||||
|
||||
const buildNetworkLinks = ( |
||||
configStepCount: number, |
||||
networkShortName: string, |
||||
interfaces: InitializeStrikerNetworkForm['interfaces'], |
||||
) => |
||||
interfaces.reduce<string>((reduceContainer, iface, index) => { |
||||
let result = reduceContainer; |
||||
|
||||
if (iface) { |
||||
const { networkInterfaceMACAddress } = iface; |
||||
|
||||
result += ` |
||||
${fvar( |
||||
configStepCount, |
||||
`${networkShortName}_link${index + 1}_mac_to_set`, |
||||
)}=${networkInterfaceMACAddress}`;
|
||||
} |
||||
|
||||
return result; |
||||
}, ''); |
||||
|
||||
export const configStriker: RequestHandler< |
||||
unknown, |
||||
undefined, |
||||
InitializeStrikerForm |
||||
> = ({ body }, response) => { |
||||
console.log('Begin initialize Striker.'); |
||||
console.dir(body, { depth: null }); |
||||
|
||||
const { |
||||
adminPassword = '', |
||||
domainName = '', |
||||
hostName = '', |
||||
hostNumber = 0, |
||||
networkDNS = '', |
||||
networkGateway = '', |
||||
networks = [], |
||||
organizationName = '', |
||||
organizationPrefix = '', |
||||
} = body || {}; |
||||
|
||||
const dataAdminPassword = String(adminPassword); |
||||
const dataDomainName = String(domainName); |
||||
const dataHostName = String(hostName); |
||||
const dataHostNumber = String(hostNumber); |
||||
const dataNetworkDNS = String(networkDNS); |
||||
const dataNetworkGateway = String(networkGateway); |
||||
const dataOrganizationName = String(organizationName); |
||||
const dataOrganizationPrefix = String(organizationPrefix); |
||||
|
||||
try { |
||||
assert( |
||||
!/['"/\\><}{]/g.test(dataAdminPassword), |
||||
`Data admin password cannot contain single-quote, double-quote, slash, backslash, angle brackets, and curly brackets; got [${dataAdminPassword}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_DOMAIN.test(dataDomainName), |
||||
`Data domain name can only contain alphanumeric, hyphen, and dot characters; got [${dataDomainName}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_DOMAIN.test(dataHostName), |
||||
`Data host name can only contain alphanumeric, hyphen, and dot characters; got [${dataHostName}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_INTEGER.test(dataHostNumber) && hostNumber > 0, |
||||
`Data host number can only contain digits; got [${dataHostNumber}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_IPV4_CSV.test(dataNetworkDNS), |
||||
`Data network DNS must be a comma separated list of valid IPv4 addresses; got [${dataNetworkDNS}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_IPV4.test(dataNetworkGateway), |
||||
`Data network gateway must be a valid IPv4 address; got [${dataNetworkGateway}]`, |
||||
); |
||||
|
||||
assert( |
||||
dataOrganizationName.length > 0, |
||||
`Data organization name cannot be empty; got [${dataOrganizationName}]`, |
||||
); |
||||
|
||||
assert( |
||||
/^[a-z0-9]{1,5}$/.test(dataOrganizationPrefix), |
||||
`Data organization prefix can only contain 1 to 5 lowercase alphanumeric characters; got [${dataOrganizationPrefix}]`, |
||||
); |
||||
} catch (assertError) { |
||||
console.log( |
||||
`Failed to assert value when trying to initialize striker; CAUSE: ${assertError}.`, |
||||
); |
||||
|
||||
response.status(400).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: SERVER_PATHS.usr.sbin['anvil-configure-host'].self, |
||||
job_data: `${fvar(1, 'domain')}=${domainName} |
||||
${fvar(1, 'organization')}=${organizationName} |
||||
${fvar(1, 'prefix')}=${organizationPrefix} |
||||
${fvar(1, 'sequence')}=${hostNumber} |
||||
${fvar(2, 'dns')}=${networkDNS} |
||||
${fvar(2, 'gateway')}=${networkGateway} |
||||
${fvar(2, 'host_name')}=${hostName} |
||||
${fvar(2, 'striker_password')}=${adminPassword} |
||||
${fvar(2, 'striker_user')}=admin${ |
||||
networks.reduce<{ |
||||
counters: Record<InitializeStrikerNetworkForm['type'], number>; |
||||
result: string; |
||||
}>( |
||||
(reduceContainer, { interfaces, ipAddress, subnetMask, type }) => { |
||||
const { counters } = reduceContainer; |
||||
|
||||
counters[type] = counters[type] ? counters[type] + 1 : 1; |
||||
|
||||
const networkShortName = `${type}${counters[type]}`; |
||||
|
||||
reduceContainer.result += ` |
||||
${fvar(2, `${networkShortName}_ip`)}=${ipAddress} |
||||
${fvar(2, `${networkShortName}_subnet_mask`)}=${subnetMask} |
||||
${buildNetworkLinks(2, networkShortName, interfaces)}`;
|
||||
|
||||
return reduceContainer; |
||||
}, |
||||
{ counters: {}, result: '' }, |
||||
).result |
||||
}`,
|
||||
job_name: 'configure::network', |
||||
job_title: 'job_0001', |
||||
job_description: 'job_0071', |
||||
}); |
||||
} catch (subError) { |
||||
console.log(`Failed to queue striker initialization; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
response.status(200).send(); |
||||
}; |
@ -0,0 +1,8 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { buildBranchRequestHandler } from '../buildBranchRequestHandler'; |
||||
import { configStriker } from './configStriker'; |
||||
|
||||
export const createHost: RequestHandler = buildBranchRequestHandler({ |
||||
striker: configStriker, |
||||
}); |
@ -0,0 +1,190 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { |
||||
getAnvilData, |
||||
getLocalHostUUID, |
||||
getPeerData, |
||||
job, |
||||
sub, |
||||
} from '../../accessModule'; |
||||
import { sanitize } from '../../sanitize'; |
||||
import { rm, stderr, stdoutVar } from '../../shell'; |
||||
|
||||
export const createHostConnection: RequestHandler< |
||||
unknown, |
||||
undefined, |
||||
CreateHostConnectionRequestBody |
||||
> = (request, response) => { |
||||
const { |
||||
body: { |
||||
dbName = 'anvil', |
||||
ipAddress, |
||||
isPing = false, |
||||
password, |
||||
port = 5432, |
||||
sshPort = 22, |
||||
user = 'admin', |
||||
}, |
||||
} = request; |
||||
|
||||
const commonDBName = sanitize(dbName, 'string'); |
||||
const commonIsPing = sanitize(isPing, 'boolean'); |
||||
const commonPassword = sanitize(password, 'string'); |
||||
const commonDBPort = sanitize(port, 'number'); |
||||
const commonDBUser = sanitize(user, 'string'); |
||||
const peerIPAddress = sanitize(ipAddress, 'string'); |
||||
const peerSSHPort = sanitize(sshPort, 'number'); |
||||
|
||||
const commonPing = commonIsPing ? 1 : 0; |
||||
|
||||
let localDBPort: number; |
||||
let localIPAddress: string; |
||||
let isPeerReachable = false; |
||||
let isPeerDBReachable = false; |
||||
let peerHostUUID: string; |
||||
|
||||
try { |
||||
({ hostUUID: peerHostUUID, isConnected: isPeerReachable } = getPeerData( |
||||
peerIPAddress, |
||||
{ password: commonPassword, port: peerSSHPort }, |
||||
)); |
||||
} catch (subError) { |
||||
stderr(`Failed to get peer data; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
stdoutVar({ peerHostUUID, isPeerReachable }); |
||||
|
||||
if (!isPeerReachable) { |
||||
stderr( |
||||
`Cannot connect to peer; please verify credentials and SSH keys validity.`, |
||||
); |
||||
|
||||
response.status(400).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
try { |
||||
localIPAddress = sub('find_matching_ip', { |
||||
subModuleName: 'System', |
||||
subParams: { host: peerIPAddress }, |
||||
}).stdout; |
||||
} catch (subError) { |
||||
stderr(`Failed to get matching IP address; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
stdoutVar({ localIPAddress }); |
||||
|
||||
const pgpassFilePath = '/tmp/.pgpass'; |
||||
const pgpassFileBody = `${peerIPAddress}:${commonDBPort}:${commonDBName}:${commonDBUser}:${commonPassword.replace( |
||||
/:/g, |
||||
'\\:', |
||||
)}`;
|
||||
|
||||
stdoutVar({ pgpassFilePath, pgpassFileBody }); |
||||
|
||||
try { |
||||
sub('write_file', { |
||||
subModuleName: 'Storage', |
||||
subParams: { |
||||
body: pgpassFileBody, |
||||
file: pgpassFilePath, |
||||
mode: '0600', |
||||
overwrite: 1, |
||||
secure: 1, |
||||
}, |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to write ${pgpassFilePath}; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
try { |
||||
const [rawIsPeerDBReachable] = sub('call', { |
||||
subModuleName: 'System', |
||||
subParams: { |
||||
shell_call: `PGPASSFILE="${pgpassFilePath}" ${SERVER_PATHS.usr.bin.psql.self} --host ${peerIPAddress} --port ${commonDBPort} --dbname ${commonDBName} --username ${commonDBUser} --no-password --tuples-only --no-align --command "SELECT 1"`, |
||||
}, |
||||
}).stdout as [output: string, returnCode: number]; |
||||
|
||||
isPeerDBReachable = rawIsPeerDBReachable === '1'; |
||||
} catch (subError) { |
||||
stderr(`Failed to test connection to peer database; CAUSE: ${subError}`); |
||||
} |
||||
|
||||
try { |
||||
rm(pgpassFilePath); |
||||
} catch (fsError) { |
||||
stderr(`Failed to remove ${pgpassFilePath}; CAUSE: ${fsError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
stdoutVar({ isPeerDBReachable }); |
||||
|
||||
if (!isPeerDBReachable) { |
||||
stderr( |
||||
`Cannot connect to peer database; please verify database credentials.`, |
||||
); |
||||
|
||||
response.status(400).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
const localHostUUID = getLocalHostUUID(); |
||||
|
||||
try { |
||||
const { |
||||
database: { |
||||
[localHostUUID]: { port: rawLocalDBPort }, |
||||
}, |
||||
} = getAnvilData({ database: true }) as { database: DatabaseHash }; |
||||
|
||||
localDBPort = sanitize(rawLocalDBPort, 'number'); |
||||
} catch (subError) { |
||||
stderr(`Failed to get local database data from hash; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
const jobCommand = `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --add --host-uuid ${peerHostUUID} --host ${peerIPAddress} --port ${commonDBPort} --ping ${commonPing}`; |
||||
const peerJobCommand = `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --add --host-uuid ${localHostUUID} --host ${localIPAddress} --port ${localDBPort} --ping ${commonPing}`; |
||||
|
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: jobCommand, |
||||
job_data: `password=${commonPassword} |
||||
peer_job_command=${peerJobCommand}`,
|
||||
job_description: 'job_0012', |
||||
job_name: 'striker-peer::add', |
||||
job_title: 'job_0011', |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to add peer ${peerHostUUID}; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
response.status(201).send(); |
||||
}; |
@ -0,0 +1,42 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { job } from '../../accessModule'; |
||||
import { toHostUUID } from '../../convertHostUUID'; |
||||
import { stderr } from '../../shell'; |
||||
|
||||
export const deleteHostConnection: RequestHandler< |
||||
unknown, |
||||
undefined, |
||||
DeleteHostConnectionRequestBody |
||||
> = (request, response) => { |
||||
const { body } = request; |
||||
const hostUUIDs = Object.keys(body); |
||||
|
||||
hostUUIDs.forEach((key) => { |
||||
const hostUUID = toHostUUID(key); |
||||
const peerHostUUIDs = body[key]; |
||||
|
||||
peerHostUUIDs.forEach((peerHostUUID) => { |
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --remove --host-uuid ${peerHostUUID}`, |
||||
job_description: 'job_0014', |
||||
job_host_uuid: hostUUID, |
||||
job_name: 'striker-peer::delete', |
||||
job_title: 'job_0013', |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to delete peer ${peerHostUUID}; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
response.status(204).send(); |
||||
}; |
@ -0,0 +1,48 @@ |
||||
import { getLocalHostUUID } from '../../accessModule'; |
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import { buildQueryHostDetail } from './buildQueryHostDetail'; |
||||
import { buildQueryResultReducer } from '../../buildQueryResultModifier'; |
||||
import { toLocal } from '../../convertHostUUID'; |
||||
import { getShortHostName } from '../../getShortHostName'; |
||||
import { sanitize } from '../../sanitize'; |
||||
|
||||
export const getHost = buildGetRequestHandler((request, buildQueryOptions) => { |
||||
const { hostUUIDs } = request.query; |
||||
|
||||
const localHostUUID: string = getLocalHostUUID(); |
||||
|
||||
let query = ` |
||||
SELECT |
||||
hos.host_name, |
||||
hos.host_uuid |
||||
FROM hosts AS hos;`;
|
||||
let afterQueryReturn: QueryResultModifierFunction | undefined = |
||||
buildQueryResultReducer<{ [hostUUID: string]: HostOverview }>( |
||||
(previous, [hostName, hostUUID]) => { |
||||
const key = toLocal(hostUUID, localHostUUID); |
||||
|
||||
previous[key] = { |
||||
hostName, |
||||
hostUUID, |
||||
shortHostName: getShortHostName(hostName), |
||||
}; |
||||
|
||||
return previous; |
||||
}, |
||||
{}, |
||||
); |
||||
|
||||
if (hostUUIDs) { |
||||
({ query, afterQueryReturn } = buildQueryHostDetail({ |
||||
keys: sanitize(hostUUIDs, 'string[]', { |
||||
modifierType: 'sql', |
||||
}), |
||||
})); |
||||
} |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = afterQueryReturn; |
||||
} |
||||
|
||||
return query; |
||||
}); |
@ -0,0 +1,128 @@ |
||||
import { getAnvilData, getLocalHostUUID } from '../../accessModule'; |
||||
import { buildUnknownIDCondition } from '../../buildCondition'; |
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import { toLocal } from '../../convertHostUUID'; |
||||
import { match } from '../../match'; |
||||
import { stdout } from '../../shell'; |
||||
|
||||
const buildHostConnections = ( |
||||
fromHostUUID: string, |
||||
databaseHash: DatabaseHash, |
||||
{ |
||||
defaultPort = 5432, |
||||
defaultUser = 'admin', |
||||
}: { defaultPort?: number; defaultUser?: string } = {}, |
||||
) => |
||||
Object.entries(databaseHash).reduce<HostConnectionOverview>( |
||||
(previous, [hostUUID, { host: ipAddress, ping, port: rawPort, user }]) => { |
||||
const port = parseInt(rawPort); |
||||
|
||||
if (hostUUID === fromHostUUID) { |
||||
previous.inbound.port = port; |
||||
previous.inbound.user = user; |
||||
} else { |
||||
previous.peer[ipAddress] = { |
||||
hostUUID, |
||||
ipAddress, |
||||
isPing: ping === '1', |
||||
port, |
||||
user, |
||||
}; |
||||
} |
||||
|
||||
return previous; |
||||
}, |
||||
{ |
||||
inbound: { ipAddress: {}, port: defaultPort, user: defaultUser }, |
||||
peer: {}, |
||||
}, |
||||
); |
||||
|
||||
export const getHostConnection = buildGetRequestHandler( |
||||
(request, buildQueryOptions) => { |
||||
const { hostUUIDs: rawHostUUIDs } = request.query; |
||||
|
||||
let rawDatabaseData: DatabaseHash; |
||||
|
||||
const hostUUIDField = 'ip_add.ip_address_host_uuid'; |
||||
const localHostUUID: string = getLocalHostUUID(); |
||||
const { after: condHostUUIDs, before: beforeBuildIDCond } = |
||||
buildUnknownIDCondition(rawHostUUIDs, hostUUIDField, { |
||||
onFallback: () => `${hostUUIDField} = '${localHostUUID}'`, |
||||
}); |
||||
const hostUUIDs = |
||||
beforeBuildIDCond.length > 0 ? beforeBuildIDCond : [localHostUUID]; |
||||
|
||||
const getConnectionKey = (hostUUID: string) => |
||||
toLocal(hostUUID, localHostUUID); |
||||
|
||||
stdout(`condHostUUIDs=[${condHostUUIDs}]`); |
||||
|
||||
try { |
||||
({ database: rawDatabaseData } = getAnvilData({ database: true })); |
||||
} catch (subError) { |
||||
throw new Error(`Failed to get anvil data; CAUSE: ${subError}`); |
||||
} |
||||
|
||||
const connections = hostUUIDs.reduce<{ |
||||
[hostUUID: string]: HostConnectionOverview; |
||||
}>((previous, hostUUID) => { |
||||
const connectionKey = getConnectionKey(hostUUID); |
||||
|
||||
previous[connectionKey] = buildHostConnections(hostUUID, rawDatabaseData); |
||||
|
||||
return previous; |
||||
}, {}); |
||||
|
||||
stdout(`connections=[${JSON.stringify(connections, null, 2)}]`); |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = (queryStdout) => { |
||||
let result = queryStdout; |
||||
|
||||
if (queryStdout instanceof Array) { |
||||
queryStdout.forEach( |
||||
([ipAddressUUID, hostUUID, ipAddress, network]) => { |
||||
const [, networkType, rawNetworkNumber, rawNetworkLinkNumber] = |
||||
match(network, /^([^\s]+)(\d+)_[^\s]+(\d+)$/); |
||||
const connectionKey = getConnectionKey(hostUUID); |
||||
|
||||
connections[connectionKey].inbound.ipAddress[ipAddress] = { |
||||
hostUUID, |
||||
ipAddress, |
||||
ipAddressUUID, |
||||
networkLinkNumber: parseInt(rawNetworkLinkNumber), |
||||
networkNumber: parseInt(rawNetworkNumber), |
||||
networkType, |
||||
}; |
||||
}, |
||||
); |
||||
|
||||
result = connections; |
||||
} |
||||
|
||||
return result; |
||||
}; |
||||
} |
||||
|
||||
return `SELECT
|
||||
ip_add.ip_address_uuid, |
||||
ip_add.ip_address_host_uuid, |
||||
ip_add.ip_address_address, |
||||
CASE |
||||
WHEN ip_add.ip_address_on_type = 'interface' |
||||
THEN net_int.network_interface_name |
||||
ELSE bon.bond_active_interface |
||||
END AS network_name |
||||
FROM ip_addresses AS ip_add |
||||
LEFT JOIN network_interfaces AS net_int |
||||
ON ip_add.ip_address_on_uuid = net_int.network_interface_uuid |
||||
LEFT JOIN bridges AS bri |
||||
ON ip_add.ip_address_on_uuid = bri.bridge_uuid |
||||
LEFT JOIN bonds AS bon |
||||
ON bri.bridge_uuid = bon.bond_bridge_uuid |
||||
OR ip_add.ip_address_on_uuid = bon.bond_uuid |
||||
WHERE ${condHostUUIDs} |
||||
AND ip_add.ip_address_note != 'DELETED';`;
|
||||
}, |
||||
); |
@ -0,0 +1,19 @@ |
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import { buildQueryHostDetail } from './buildQueryHostDetail'; |
||||
import { toHostUUID } from '../../convertHostUUID'; |
||||
import { sanitizeSQLParam } from '../../sanitizeSQLParam'; |
||||
|
||||
export const getHostDetail = buildGetRequestHandler( |
||||
({ params: { hostUUID: rawHostUUID } }, buildQueryOptions) => { |
||||
const hostUUID = toHostUUID(rawHostUUID); |
||||
const { afterQueryReturn, query } = buildQueryHostDetail({ |
||||
keys: [sanitizeSQLParam(hostUUID)], |
||||
}); |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = afterQueryReturn; |
||||
} |
||||
|
||||
return query; |
||||
}, |
||||
); |
@ -0,0 +1,8 @@ |
||||
export * from './createHost'; |
||||
export * from './createHostConnection'; |
||||
export * from './deleteHostConnection'; |
||||
export * from './getHost'; |
||||
export * from './getHostConnection'; |
||||
export * from './getHostDetail'; |
||||
export * from './prepareHost'; |
||||
export * from './updateHost'; |
@ -0,0 +1,150 @@ |
||||
import assert from 'assert'; |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { |
||||
REP_DOMAIN, |
||||
REP_IPV4, |
||||
REP_PEACEFUL_STRING, |
||||
REP_UUID, |
||||
} from '../../consts/REG_EXP_PATTERNS'; |
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { job, variable } from '../../accessModule'; |
||||
import { sanitize } from '../../sanitize'; |
||||
import { stderr } from '../../shell'; |
||||
|
||||
export const prepareHost: RequestHandler< |
||||
unknown, |
||||
undefined, |
||||
PrepareHostRequestBody |
||||
> = (request, response) => { |
||||
const { |
||||
body: { |
||||
enterpriseUUID, |
||||
hostIPAddress, |
||||
hostName, |
||||
hostPassword, |
||||
hostSSHPort, |
||||
hostType, |
||||
hostUser = 'root', |
||||
hostUUID, |
||||
redhatPassword, |
||||
redhatUser, |
||||
} = {}, |
||||
} = request; |
||||
|
||||
const isEnterpriseUUIDProvided = Boolean(enterpriseUUID); |
||||
const isHostUUIDProvided = Boolean(hostUUID); |
||||
const isRedhatAccountProvided = |
||||
Boolean(redhatPassword) || Boolean(redhatUser); |
||||
|
||||
const dataEnterpriseUUID = sanitize(enterpriseUUID, 'string'); |
||||
const dataHostIPAddress = sanitize(hostIPAddress, 'string'); |
||||
const dataHostName = sanitize(hostName, 'string'); |
||||
const dataHostPassword = sanitize(hostPassword, 'string'); |
||||
const dataHostSSHPort = sanitize(hostSSHPort, 'number') || 22; |
||||
const dataHostType = sanitize(hostType, 'string'); |
||||
// Host user is unused at the moment.
|
||||
const dataHostUser = sanitize(hostUser, 'string'); |
||||
const dataHostUUID = sanitize(hostUUID, 'string'); |
||||
const dataRedhatPassword = sanitize(redhatPassword, 'string'); |
||||
const dataRedhatUser = sanitize(redhatUser, 'string'); |
||||
|
||||
try { |
||||
assert( |
||||
REP_IPV4.test(dataHostIPAddress), |
||||
`Data host IP address must be a valid IPv4 address; got [${dataHostIPAddress}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_DOMAIN.test(dataHostName), |
||||
`Data host name can only contain alphanumeric, hyphen, and dot characters; got [${dataHostName}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_PEACEFUL_STRING.test(dataHostPassword), |
||||
`Data host password must be peaceful string; got [${dataHostPassword}]`, |
||||
); |
||||
|
||||
assert( |
||||
/^node|dr$/.test(dataHostType), |
||||
`Data host type must be one of "node" or "dr"; got [${dataHostType}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_PEACEFUL_STRING.test(dataHostUser), |
||||
`Data host user must be a peaceful string; got [${dataHostUser}]`, |
||||
); |
||||
|
||||
if (isEnterpriseUUIDProvided) { |
||||
assert( |
||||
REP_UUID.test(dataEnterpriseUUID), |
||||
`Data enterprise UUID must be a valid UUIDv4; got [${dataEnterpriseUUID}]`, |
||||
); |
||||
} |
||||
|
||||
if (isHostUUIDProvided) { |
||||
assert( |
||||
REP_UUID.test(dataHostUUID), |
||||
`Data host UUID must be a valid UUIDv4; got [${dataHostUUID}]`, |
||||
); |
||||
} |
||||
|
||||
if (isRedhatAccountProvided) { |
||||
assert( |
||||
REP_PEACEFUL_STRING.test(dataRedhatPassword), |
||||
`Data redhat password must be a peaceful string; got [${dataRedhatPassword}]`, |
||||
); |
||||
|
||||
assert( |
||||
REP_PEACEFUL_STRING.test(dataRedhatUser), |
||||
`Data redhat user must be a peaceful string; got [${dataRedhatUser}]`, |
||||
); |
||||
} |
||||
} catch (assertError) { |
||||
stderr( |
||||
`Failed to assert value when trying to prepare host; CAUSE: ${assertError}`, |
||||
); |
||||
|
||||
response.status(400).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
try { |
||||
if (isHostUUIDProvided) { |
||||
variable({ |
||||
file: __filename, |
||||
update_value_only: 1, |
||||
variable_name: 'system::configured', |
||||
variable_source_table: 'hosts', |
||||
variable_source_uuid: dataHostUUID, |
||||
variable_value: 0, |
||||
}); |
||||
} |
||||
|
||||
job({ |
||||
file: __filename, |
||||
job_command: SERVER_PATHS.usr.sbin['striker-initialize-host'].self, |
||||
job_data: `enterprise_uuid=${dataEnterpriseUUID} |
||||
host_ip_address=${dataHostIPAddress} |
||||
host_name=${dataHostName} |
||||
password=${dataHostPassword} |
||||
rh_password=${dataRedhatPassword} |
||||
rh_user=${dataRedhatUser} |
||||
ssh_port=${dataHostSSHPort} |
||||
type=${dataHostType}`,
|
||||
job_description: 'job_0022', |
||||
job_name: `initialize::${dataHostType}::${dataHostIPAddress}`, |
||||
job_title: `job_002${dataHostType === 'dr' ? '1' : '0'}`, |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to init host; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
response.status(200).send(); |
||||
}; |
@ -0,0 +1,39 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { LOCAL } from '../../consts/LOCAL'; |
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { job } from '../../accessModule'; |
||||
import { stderr, stdout } from '../../shell'; |
||||
|
||||
export const setHostInstallTarget: RequestHandler = (request, response) => { |
||||
stdout( |
||||
`Begin set host install target.\n${JSON.stringify(request.body, null, 2)}`, |
||||
); |
||||
|
||||
const { isEnableInstallTarget } = |
||||
request.body as SetHostInstallTargetRequestBody; |
||||
const { hostUUID: rawHostUUID } = request.params as UpdateHostParams; |
||||
const hostUUID: string | undefined = |
||||
rawHostUUID === LOCAL ? undefined : rawHostUUID; |
||||
const task = isEnableInstallTarget ? 'enable' : 'disable'; |
||||
|
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: `${SERVER_PATHS.usr.sbin['striker-manage-install-target'].self} --${task}`, |
||||
job_description: 'job_0016', |
||||
job_host_uuid: hostUUID, |
||||
job_name: `install-target::${task}`, |
||||
job_title: 'job_0015', |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to ${task} install target; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
response.status(200).send(); |
||||
}; |
@ -0,0 +1,8 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { buildBranchRequestHandler } from '../buildBranchRequestHandler'; |
||||
import { setHostInstallTarget } from './setHostInstallTarget'; |
||||
|
||||
export const updateHost: RequestHandler = buildBranchRequestHandler({ |
||||
'install-target': setHostInstallTarget, |
||||
}); |
@ -0,0 +1,82 @@ |
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import { sanitize } from '../../sanitize'; |
||||
import { date, stdout } from '../../shell'; |
||||
|
||||
export const getJob = buildGetRequestHandler((request, buildQueryOptions) => { |
||||
const { start: rawStart } = request.query; |
||||
|
||||
const start = sanitize(rawStart, 'number'); |
||||
|
||||
let condModifiedDate = ''; |
||||
|
||||
try { |
||||
const minDate = date('--date', `@${start}`, '--rfc-3339', 'ns'); |
||||
|
||||
condModifiedDate = `OR (job.job_progress = 100 AND job.modified_date >= '${minDate}')`; |
||||
} catch (shellError) { |
||||
throw new Error( |
||||
`Failed to build date condition for job query; CAUSE: ${shellError}`, |
||||
); |
||||
} |
||||
|
||||
stdout(`condModifiedDate=[${condModifiedDate}]`); |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = (queryStdout) => { |
||||
let result = queryStdout; |
||||
|
||||
if (queryStdout instanceof Array) { |
||||
result = queryStdout.reduce<{ |
||||
[jobUUID: string]: { |
||||
jobCommand: string; |
||||
jobHostName: string; |
||||
jobHostUUID: string; |
||||
jobName: string; |
||||
jobProgress: number; |
||||
jobUUID: string; |
||||
}; |
||||
}>( |
||||
( |
||||
previous, |
||||
[ |
||||
jobUUID, |
||||
jobName, |
||||
jobHostUUID, |
||||
jobHostName, |
||||
jobCommand, |
||||
rawJobProgress, |
||||
], |
||||
) => { |
||||
previous[jobUUID] = { |
||||
jobCommand, |
||||
jobHostName, |
||||
jobHostUUID, |
||||
jobName, |
||||
jobProgress: parseFloat(rawJobProgress), |
||||
jobUUID, |
||||
}; |
||||
|
||||
return previous; |
||||
}, |
||||
{}, |
||||
); |
||||
} |
||||
|
||||
return result; |
||||
}; |
||||
} |
||||
|
||||
return ` |
||||
SELECT |
||||
job.job_uuid, |
||||
job.job_name, |
||||
job.job_host_uuid, |
||||
hos.host_name, |
||||
job.job_command, |
||||
job.job_progress |
||||
FROM jobs AS job |
||||
JOIN hosts AS hos |
||||
ON job.job_host_uuid = hos.host_uuid |
||||
WHERE job.job_progress < 100 |
||||
${condModifiedDate};`;
|
||||
}); |
@ -0,0 +1 @@ |
||||
export * from './getJob'; |
@ -0,0 +1,54 @@ |
||||
import { getLocalHostUUID } from '../../accessModule'; |
||||
|
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
|
||||
export const getNetworkInterface = buildGetRequestHandler( |
||||
(request, buildQueryOptions) => { |
||||
const localHostUUID: string = getLocalHostUUID(); |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = (queryStdout) => { |
||||
let result = queryStdout; |
||||
|
||||
if (queryStdout instanceof Array) { |
||||
result = queryStdout.map<NetworkInterfaceOverview>( |
||||
([ |
||||
networkInterfaceUUID, |
||||
networkInterfaceMACAddress, |
||||
networkInterfaceName, |
||||
networkInterfaceState, |
||||
networkInterfaceSpeed, |
||||
networkInterfaceOrder, |
||||
]) => ({ |
||||
networkInterfaceUUID, |
||||
networkInterfaceMACAddress, |
||||
networkInterfaceName, |
||||
networkInterfaceState, |
||||
networkInterfaceSpeed, |
||||
networkInterfaceOrder, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
return result; |
||||
}; |
||||
} |
||||
|
||||
return ` |
||||
SELECT |
||||
network_interface_uuid, |
||||
network_interface_mac_address, |
||||
network_interface_name, |
||||
CASE |
||||
WHEN network_interface_link_state = '1' |
||||
AND network_interface_operational = 'up' |
||||
THEN 'up' |
||||
ELSE 'down' |
||||
END AS network_interface_state, |
||||
network_interface_speed, |
||||
ROW_NUMBER() OVER(ORDER BY modified_date ASC) AS network_interface_order |
||||
FROM network_interfaces |
||||
WHERE network_interface_operational != 'DELETE' |
||||
AND network_interface_host_uuid = '${localHostUUID}';`;
|
||||
}, |
||||
); |
@ -0,0 +1 @@ |
||||
export { getNetworkInterface } from './getNetworkInterface'; |
@ -0,0 +1,158 @@ |
||||
import assert from 'assert'; |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
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'; |
||||
|
||||
export const createServer: RequestHandler = ({ body }, response) => { |
||||
stdout(`Creating server.\n${JSON.stringify(body, null, 2)}`); |
||||
|
||||
const { |
||||
serverName, |
||||
cpuCores, |
||||
memory, |
||||
virtualDisks: [ |
||||
{ storageSize = undefined, storageGroupUUID = 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); |
||||
|
||||
try { |
||||
assert( |
||||
/^[0-9a-z_-]+$/i.test(dataServerName), |
||||
`Data server name can only contain alphanumeric, underscore, and hyphen characters; got [${dataServerName}].`, |
||||
); |
||||
|
||||
const [[serverNameCount]] = dbQuery( |
||||
`SELECT COUNT(server_uuid) FROM servers WHERE server_name = '${dataServerName}'`, |
||||
).stdout; |
||||
|
||||
assert( |
||||
serverNameCount === 0, |
||||
`Data server name already exists; got [${dataServerName}]`, |
||||
); |
||||
assert( |
||||
OS_LIST_MAP[dataOS] !== undefined, |
||||
`Data OS not recognized; got [${dataOS}].`, |
||||
); |
||||
assert( |
||||
REP_INTEGER.test(dataCPUCores), |
||||
`Data CPU cores can only contain digits; got [${dataCPUCores}].`, |
||||
); |
||||
assert( |
||||
REP_INTEGER.test(dataRAM), |
||||
`Data RAM can only contain digits; got [${dataRAM}].`, |
||||
); |
||||
assert( |
||||
REP_UUID.test(dataStorageGroupUUID), |
||||
`Data storage group UUID must be a valid UUID; got [${dataStorageGroupUUID}].`, |
||||
); |
||||
assert( |
||||
REP_INTEGER.test(dataStorageSize), |
||||
`Data storage size can only contain digits; got [${dataStorageSize}].`, |
||||
); |
||||
assert( |
||||
REP_UUID.test(dataInstallISO), |
||||
`Data install ISO must be a valid UUID; got [${dataInstallISO}].`, |
||||
); |
||||
assert( |
||||
dataDriverISO === 'none' || REP_UUID.test(dataDriverISO), |
||||
`Data driver ISO must be a valid UUID when provided; got [${dataDriverISO}].`, |
||||
); |
||||
assert( |
||||
REP_UUID.test(dataAnvilUUID), |
||||
`Data anvil UUID must be a valid UUID; got [${dataAnvilUUID}].`, |
||||
); |
||||
} catch (assertError) { |
||||
stdout( |
||||
`Failed to assert value when trying to provision a server; CAUSE: ${assertError}.`, |
||||
); |
||||
|
||||
response.status(400).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
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}`;
|
||||
|
||||
stdout(`provisionServerJobData=[${provisionServerJobData}]`); |
||||
|
||||
const [[provisionServerJobHostUUID]] = dbQuery( |
||||
`SELECT
|
||||
CASE |
||||
WHEN pri_hos.primary_host_uuid IS NULL |
||||
THEN nod_1.node1_host_uuid |
||||
ELSE pri_hos.primary_host_uuid |
||||
END AS host_uuid |
||||
FROM ( |
||||
SELECT |
||||
1 AS phl, |
||||
sca_clu_nod.scan_cluster_node_host_uuid AS primary_host_uuid |
||||
FROM anvils AS anv |
||||
JOIN scan_cluster_nodes AS sca_clu_nod |
||||
ON sca_clu_nod.scan_cluster_node_host_uuid = anv.anvil_node1_host_uuid |
||||
OR sca_clu_nod.scan_cluster_node_host_uuid = anv.anvil_node2_host_uuid |
||||
WHERE sca_clu_nod.scan_cluster_node_in_ccm |
||||
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}' |
||||
ORDER BY sca_clu_nod.scan_cluster_node_name |
||||
LIMIT 1 |
||||
) AS pri_hos |
||||
RIGHT JOIN ( |
||||
SELECT |
||||
1 AS phr, |
||||
anv.anvil_node1_host_uuid AS node1_host_uuid |
||||
FROM anvils AS anv |
||||
WHERE anv.anvil_uuid = '${dataAnvilUUID}' |
||||
) AS nod_1 |
||||
ON pri_hos.phl = nod_1.phr;`,
|
||||
).stdout; |
||||
|
||||
stdout(`provisionServerJobHostUUID=[${provisionServerJobHostUUID}]`); |
||||
|
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: SERVER_PATHS.usr.sbin['anvil-provision-server'].self, |
||||
job_data: provisionServerJobData, |
||||
job_name: 'server:provision', |
||||
job_title: 'job_0147', |
||||
job_description: 'job_0148', |
||||
job_host_uuid: provisionServerJobHostUUID, |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to provision server; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
response.status(202).send(); |
||||
}; |
@ -0,0 +1,60 @@ |
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import join from '../../join'; |
||||
import { sanitize } from '../../sanitize'; |
||||
|
||||
export const getServer = buildGetRequestHandler( |
||||
(request, buildQueryOptions) => { |
||||
const { anvilUUIDs } = request.query; |
||||
|
||||
const condAnvilUUIDs = join(sanitize(anvilUUIDs, 'string[]'), { |
||||
beforeReturn: (toReturn) => |
||||
toReturn ? `AND ser.server_anvil_uuid IN (${toReturn})` : '', |
||||
elementWrapper: "'", |
||||
separator: ', ', |
||||
}); |
||||
|
||||
console.log(`condAnvilsUUID=[${condAnvilUUIDs}]`); |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = (queryStdout) => { |
||||
let result = queryStdout; |
||||
|
||||
if (queryStdout instanceof Array) { |
||||
result = queryStdout.map<ServerOverview>( |
||||
([ |
||||
serverUUID, |
||||
serverName, |
||||
serverState, |
||||
serverHostUUID, |
||||
anvilUUID, |
||||
anvilName, |
||||
]) => ({ |
||||
serverHostUUID, |
||||
serverName, |
||||
serverState, |
||||
serverUUID, |
||||
anvilUUID, |
||||
anvilName, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
return result; |
||||
}; |
||||
} |
||||
|
||||
return ` |
||||
SELECT |
||||
ser.server_uuid, |
||||
ser.server_name, |
||||
ser.server_state, |
||||
ser.server_host_uuid, |
||||
anv.anvil_uuid, |
||||
anv.anvil_name |
||||
FROM servers AS ser |
||||
JOIN anvils AS anv |
||||
ON ser.server_anvil_uuid = anv.anvil_uuid |
||||
WHERE ser.server_state != 'DELETED' |
||||
${condAnvilUUIDs};`;
|
||||
}, |
||||
); |
@ -0,0 +1,152 @@ |
||||
import assert from 'assert'; |
||||
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 { dbQuery, getLocalHostUUID, job } from '../../accessModule'; |
||||
import { sanitize } from '../../sanitize'; |
||||
import { mkfifo, rm } from '../../shell'; |
||||
|
||||
export const getServerDetail: RequestHandler = (request, response) => { |
||||
const { serverUUID } = request.params; |
||||
const { ss, resize } = request.query; |
||||
|
||||
const epoch = Date.now(); |
||||
const isScreenshot = sanitize(ss, 'boolean'); |
||||
|
||||
console.log( |
||||
`serverUUID=[${serverUUID}],epoch=[${epoch}],isScreenshot=[${isScreenshot}]`, |
||||
); |
||||
|
||||
try { |
||||
assert( |
||||
REP_UUID.test(serverUUID), |
||||
`Server UUID must be a valid UUID; got [${serverUUID}]`, |
||||
); |
||||
} catch (assertError) { |
||||
console.log( |
||||
`Failed to assert value when trying to get server detail; CAUSE: ${assertError}.`, |
||||
); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
if (isScreenshot) { |
||||
let requestHostUUID: string, serverHostUUID: string; |
||||
|
||||
try { |
||||
requestHostUUID = getLocalHostUUID(); |
||||
} catch (subError) { |
||||
console.log(subError); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
console.log(`requestHostUUID=[${requestHostUUID}]`); |
||||
|
||||
try { |
||||
[[serverHostUUID]] = dbQuery(` |
||||
SELECT server_host_uuid |
||||
FROM servers |
||||
WHERE server_uuid = '${serverUUID}';`).stdout;
|
||||
} catch (queryError) { |
||||
console.log(`Failed to get server host UUID; CAUSE: ${queryError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
console.log(`serverHostUUID=[${serverHostUUID}]`); |
||||
|
||||
const imageFileName = `${serverUUID}_screenshot_${epoch}`; |
||||
const imageFilePath = path.join(SERVER_PATHS.tmp.self, imageFileName); |
||||
|
||||
try { |
||||
mkfifo(imageFilePath); |
||||
|
||||
const namedPipeReadStream = createReadStream(imageFilePath, { |
||||
autoClose: true, |
||||
encoding: 'utf-8', |
||||
}); |
||||
|
||||
let imageData = ''; |
||||
|
||||
namedPipeReadStream.once('close', () => { |
||||
console.log(`On close; removing named pipe at ${imageFilePath}.`); |
||||
|
||||
try { |
||||
rm(imageFilePath); |
||||
} catch (cleanPipeError) { |
||||
console.log( |
||||
`Failed to clean up named pipe; CAUSE: ${cleanPipeError}`, |
||||
); |
||||
} |
||||
}); |
||||
|
||||
namedPipeReadStream.once('end', () => { |
||||
response.status(200).send({ screenshot: imageData }); |
||||
}); |
||||
|
||||
namedPipeReadStream.on('data', (data) => { |
||||
const imageChunk = data.toString().trim(); |
||||
const chunkLogLength = 10; |
||||
|
||||
console.log( |
||||
`${serverUUID} image chunk: ${imageChunk.substring( |
||||
0, |
||||
chunkLogLength, |
||||
)}...${imageChunk.substring(imageChunk.length - chunkLogLength - 1)}`,
|
||||
); |
||||
|
||||
imageData += imageChunk; |
||||
}); |
||||
} catch (prepPipeError) { |
||||
console.log(`Failed to prepare named pipe; CAUSE: ${prepPipeError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
let resizeArgs = sanitize(resize, 'string'); |
||||
|
||||
if (!/^\d+x\d+$/.test(resizeArgs)) { |
||||
resizeArgs = ''; |
||||
} |
||||
|
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: SERVER_PATHS.usr.sbin['anvil-get-server-screenshot'].self, |
||||
job_data: `server-uuid=${serverUUID} |
||||
request-host-uuid=${requestHostUUID} |
||||
resize=${resizeArgs} |
||||
out-file-id=${epoch}`,
|
||||
job_name: `get_server_screenshot::${serverUUID}::${epoch}`, |
||||
job_title: 'job_0356', |
||||
job_description: 'job_0357', |
||||
job_host_uuid: serverHostUUID, |
||||
}); |
||||
} catch (subError) { |
||||
console.log( |
||||
`Failed to queue fetch server screenshot job; CAUSE: ${subError}`, |
||||
); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
} else { |
||||
// For getting sever detail data.
|
||||
|
||||
response.status(200).send(); |
||||
} |
||||
}; |
@ -0,0 +1,3 @@ |
||||
export { createServer } from './createServer'; |
||||
export { getServer } from './getServer'; |
||||
export { getServerDetail } from './getServerDetail'; |
@ -0,0 +1,41 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import SERVER_PATHS from '../../consts/SERVER_PATHS'; |
||||
|
||||
import { job } from '../../accessModule'; |
||||
import { toHostUUID } from '../../convertHostUUID'; |
||||
import { stderr } from '../../shell'; |
||||
|
||||
export const deleteSSHKeyConflict: RequestHandler< |
||||
unknown, |
||||
undefined, |
||||
DeleteSSHKeyConflictRequestBody |
||||
> = (request, response) => { |
||||
const { body } = request; |
||||
const hostUUIDs = Object.keys(body); |
||||
|
||||
hostUUIDs.forEach((key) => { |
||||
const hostUUID = toHostUUID(key); |
||||
const stateUUIDs = body[key]; |
||||
|
||||
try { |
||||
job({ |
||||
file: __filename, |
||||
job_command: SERVER_PATHS.usr.sbin['anvil-manage-keys'].self, |
||||
job_data: stateUUIDs.join(','), |
||||
job_description: 'job_0057', |
||||
job_host_uuid: hostUUID, |
||||
job_name: 'manage::broken_keys', |
||||
job_title: 'job_0056', |
||||
}); |
||||
} catch (subError) { |
||||
stderr(`Failed to delete bad SSH keys; CAUSE: ${subError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
}); |
||||
|
||||
response.status(204).send(); |
||||
}; |
@ -0,0 +1,66 @@ |
||||
import { HOST_KEY_CHANGED_PREFIX } from '../../consts/HOST_KEY_CHANGED_PREFIX'; |
||||
|
||||
import { getLocalHostUUID } from '../../accessModule'; |
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import { buildQueryResultReducer } from '../../buildQueryResultModifier'; |
||||
import { toLocal } from '../../convertHostUUID'; |
||||
import { match } from '../../match'; |
||||
|
||||
export const getSSHKeyConflict = buildGetRequestHandler( |
||||
(request, buildQueryOptions) => { |
||||
const localHostUUID: string = getLocalHostUUID(); |
||||
|
||||
const query = ` |
||||
SELECT |
||||
hos.host_name, |
||||
hos.host_uuid, |
||||
sta.state_name, |
||||
sta.state_note, |
||||
sta.state_uuid |
||||
FROM states AS sta |
||||
JOIN hosts AS hos |
||||
ON sta.state_host_uuid = hos.host_uuid |
||||
WHERE sta.state_name LIKE '${HOST_KEY_CHANGED_PREFIX}%';`;
|
||||
const afterQueryReturn = buildQueryResultReducer<{ |
||||
[hostUUID: string]: { |
||||
[stateUUID: string]: { |
||||
badFile: string; |
||||
badLine: number; |
||||
hostName: string; |
||||
hostUUID: string; |
||||
ipAddress: string; |
||||
stateUUID: string; |
||||
}; |
||||
}; |
||||
}>((previous, [hostName, hostUUID, stateName, stateNote, stateUUID]) => { |
||||
const hostUUIDKey = toLocal(hostUUID, localHostUUID); |
||||
|
||||
if (previous[hostUUIDKey] === undefined) { |
||||
previous[hostUUIDKey] = {}; |
||||
} |
||||
|
||||
const ipAddress = stateName.slice(HOST_KEY_CHANGED_PREFIX.length); |
||||
const [, badFile, badLine = '0'] = match( |
||||
stateNote, |
||||
/file=([^\s]+),line=(\d+)/, |
||||
); |
||||
|
||||
previous[hostUUIDKey][stateUUID] = { |
||||
badFile, |
||||
badLine: parseInt(badLine), |
||||
hostName, |
||||
hostUUID, |
||||
ipAddress, |
||||
stateUUID, |
||||
}; |
||||
|
||||
return previous; |
||||
}, {}); |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = afterQueryReturn; |
||||
} |
||||
|
||||
return query; |
||||
}, |
||||
); |
@ -0,0 +1,2 @@ |
||||
export * from './deleteSSHKeyConflict'; |
||||
export * from './getSSHKeyConflict'; |
@ -0,0 +1,27 @@ |
||||
import buildGetRequestHandler from '../buildGetRequestHandler'; |
||||
import { buildQueryResultReducer } from '../../buildQueryResultModifier'; |
||||
|
||||
export const getUser = buildGetRequestHandler((request, buildQueryOptions) => { |
||||
const query = ` |
||||
SELECT |
||||
use.user_name, |
||||
use.user_uuid |
||||
FROM users AS use;`;
|
||||
const afterQueryReturn: QueryResultModifierFunction | undefined = |
||||
buildQueryResultReducer< |
||||
Record<string, { userName: string; userUUID: string }> |
||||
>((previous, [userName, userUUID]) => { |
||||
previous[userUUID] = { |
||||
userName, |
||||
userUUID, |
||||
}; |
||||
|
||||
return previous; |
||||
}, {}); |
||||
|
||||
if (buildQueryOptions) { |
||||
buildQueryOptions.afterQueryReturn = afterQueryReturn; |
||||
} |
||||
|
||||
return query; |
||||
}); |
@ -0,0 +1 @@ |
||||
export * from './getUser'; |
@ -0,0 +1,71 @@ |
||||
import call from './call'; |
||||
import { sanitizeSQLParam } from './sanitizeSQLParam'; |
||||
|
||||
type MapToReturnType = { |
||||
boolean: boolean; |
||||
number: number; |
||||
string: string; |
||||
'string[]': string[]; |
||||
}; |
||||
|
||||
type MapToReturnFunction = { |
||||
[ReturnTypeName in keyof MapToReturnType]: ( |
||||
value: unknown, |
||||
modifier: (unmodified: unknown) => string, |
||||
) => MapToReturnType[ReturnTypeName]; |
||||
}; |
||||
|
||||
type ModifierFunction = (unmodified: string) => string; |
||||
|
||||
type MapToModifierFunction = { |
||||
none: undefined; |
||||
sql: ModifierFunction; |
||||
}; |
||||
|
||||
const MAP_TO_MODIFIER_FUNCTION: MapToModifierFunction = { |
||||
none: undefined, |
||||
sql: sanitizeSQLParam, |
||||
}; |
||||
|
||||
const MAP_TO_RETURN_FUNCTION: MapToReturnFunction = { |
||||
boolean: (value) => value !== undefined, |
||||
number: (value) => parseFloat(String(value)) || 0, |
||||
string: (value, mod) => (value ? mod(value) : ''), |
||||
'string[]': (value, mod) => { |
||||
let result: string[] = []; |
||||
|
||||
if (value instanceof Array) { |
||||
result = value.reduce<string[]>((reduceContainer, element) => { |
||||
if (element) { |
||||
reduceContainer.push(mod(element)); |
||||
} |
||||
|
||||
return reduceContainer; |
||||
}, []); |
||||
} else if (value) { |
||||
result = mod(value).split(/[,;]/); |
||||
} |
||||
|
||||
return result; |
||||
}, |
||||
}; |
||||
|
||||
export const sanitize = <ReturnTypeName extends keyof MapToReturnType>( |
||||
value: unknown, |
||||
returnType: ReturnTypeName, |
||||
{ |
||||
modifierType = 'none', |
||||
modifier = MAP_TO_MODIFIER_FUNCTION[modifierType], |
||||
}: { |
||||
modifier?: ModifierFunction; |
||||
modifierType?: keyof MapToModifierFunction; |
||||
} = {}, |
||||
): MapToReturnType[ReturnTypeName] => |
||||
MAP_TO_RETURN_FUNCTION[returnType](value, (unmodified: unknown) => { |
||||
const input = String(unmodified); |
||||
|
||||
return call<string>(modifier, { |
||||
notCallableReturn: input, |
||||
parameters: [input], |
||||
}); |
||||
}) as MapToReturnType[ReturnTypeName]; |
@ -0,0 +1,2 @@ |
||||
export const sanitizeSQLParam = (variable: string): string => |
||||
variable.replace(/[']/g, ''); |
@ -0,0 +1,46 @@ |
||||
import { spawnSync } from 'child_process'; |
||||
|
||||
import SERVER_PATHS from './consts/SERVER_PATHS'; |
||||
|
||||
const print = ( |
||||
message: string, |
||||
{ |
||||
eol = '\n', |
||||
stream = 'stdout', |
||||
}: { eol?: string; stream?: 'stderr' | 'stdout' } = {}, |
||||
) => process[stream].write(`${message}${eol}`); |
||||
|
||||
const systemCall = ( |
||||
...[command, args = [], options = {}]: Parameters<typeof spawnSync> |
||||
) => { |
||||
const { error, stderr, stdout } = spawnSync(command, args, { |
||||
...options, |
||||
encoding: 'utf-8', |
||||
}); |
||||
|
||||
if (error) { |
||||
throw error; |
||||
} |
||||
|
||||
if (stderr) { |
||||
throw new Error(stderr); |
||||
} |
||||
|
||||
return stdout; |
||||
}; |
||||
|
||||
export const date = (...args: string[]) => |
||||
systemCall(SERVER_PATHS.usr.bin.date.self, args); |
||||
|
||||
export const mkfifo = (...args: string[]) => |
||||
systemCall(SERVER_PATHS.usr.bin.mkfifo.self, args); |
||||
|
||||
export const rm = (...args: string[]) => |
||||
systemCall(SERVER_PATHS.usr.bin.rm.self, args); |
||||
|
||||
export const stderr = (message: string) => print(message, { stream: 'stderr' }); |
||||
|
||||
export const stdout = (message: string) => print(message); |
||||
|
||||
export const stdoutVar = (variable: { [name: string]: unknown }) => |
||||
print(`Variables: ${JSON.stringify(variable, null, 2)}`); |
@ -0,0 +1,9 @@ |
||||
import express from 'express'; |
||||
|
||||
import getAnvil from '../lib/request_handlers/anvil/getAnvil'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router.get('/', getAnvil); |
||||
|
||||
export default router; |
@ -0,0 +1,18 @@ |
||||
import express from 'express'; |
||||
|
||||
import { |
||||
getHostSSH, |
||||
poweroffHost, |
||||
rebootHost, |
||||
updateSystem, |
||||
} from '../lib/request_handlers/command'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router |
||||
.put('/inquire-host', getHostSSH) |
||||
.put('/poweroff-host', poweroffHost) |
||||
.put('/reboot-host', rebootHost) |
||||
.put('/update-system', updateSystem); |
||||
|
||||
export default router; |
@ -0,0 +1,28 @@ |
||||
import express from 'express'; |
||||
|
||||
import { |
||||
createHost, |
||||
createHostConnection, |
||||
deleteHostConnection, |
||||
getHost, |
||||
getHostConnection, |
||||
getHostDetail, |
||||
prepareHost, |
||||
updateHost, |
||||
} from '../lib/request_handlers/host'; |
||||
|
||||
const CONNECTION_PATH = '/connection'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router |
||||
.get('/', getHost) |
||||
.get(CONNECTION_PATH, getHostConnection) |
||||
.get('/:hostUUID', getHostDetail) |
||||
.post('/', createHost) |
||||
.post(CONNECTION_PATH, createHostConnection) |
||||
.put('/prepare', prepareHost) |
||||
.put('/:hostUUID', updateHost) |
||||
.delete(CONNECTION_PATH, deleteHostConnection); |
||||
|
||||
export default router; |
@ -0,0 +1,27 @@ |
||||
import { Router } from 'express'; |
||||
|
||||
import anvilRouter from './anvil'; |
||||
import commandRouter from './command'; |
||||
import echoRouter from './echo'; |
||||
import fileRouter from './file'; |
||||
import hostRouter from './host'; |
||||
import jobRouter from './job'; |
||||
import networkInterfaceRouter from './network-interface'; |
||||
import serverRouter from './server'; |
||||
import sshKeyRouter from './ssh-key'; |
||||
import userRouter from './user'; |
||||
|
||||
const routes: Readonly<Record<string, Router>> = { |
||||
anvil: anvilRouter, |
||||
command: commandRouter, |
||||
echo: echoRouter, |
||||
file: fileRouter, |
||||
host: hostRouter, |
||||
job: jobRouter, |
||||
'network-interface': networkInterfaceRouter, |
||||
server: serverRouter, |
||||
'ssh-key': sshKeyRouter, |
||||
user: userRouter, |
||||
}; |
||||
|
||||
export default routes; |
@ -0,0 +1,9 @@ |
||||
import express from 'express'; |
||||
|
||||
import { getJob } from '../lib/request_handlers/job'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router.get('/', getJob); |
||||
|
||||
export default router; |
@ -0,0 +1,9 @@ |
||||
import express from 'express'; |
||||
|
||||
import { getNetworkInterface } from '../lib/request_handlers/network-interface'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router.get('/', getNetworkInterface); |
||||
|
||||
export default router; |
@ -0,0 +1,16 @@ |
||||
import express from 'express'; |
||||
|
||||
import { |
||||
createServer, |
||||
getServer, |
||||
getServerDetail, |
||||
} from '../lib/request_handlers/server'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router |
||||
.get('/', getServer) |
||||
.get('/:serverUUID', getServerDetail) |
||||
.post('/', createServer); |
||||
|
||||
export default router; |
@ -0,0 +1,14 @@ |
||||
import express from 'express'; |
||||
|
||||
import { |
||||
deleteSSHKeyConflict, |
||||
getSSHKeyConflict, |
||||
} from '../lib/request_handlers/ssh-key'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router |
||||
.get('/conflict', getSSHKeyConflict) |
||||
.delete('/conflict', deleteSSHKeyConflict); |
||||
|
||||
export default router; |
@ -0,0 +1,9 @@ |
||||
import express from 'express'; |
||||
|
||||
import { getUser } from '../lib/request_handlers/user'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router.get('/', getUser); |
||||
|
||||
export default router; |
@ -0,0 +1,3 @@ |
||||
interface AnvilDataStruct { |
||||
[key: string]: AnvilDataStruct | boolean; |
||||
} |
@ -0,0 +1,33 @@ |
||||
type AnvilDetailForProvisionServer = { |
||||
anvilUUID: string; |
||||
anvilName: string; |
||||
anvilDescription: string; |
||||
hosts: Array<{ |
||||
hostUUID: string; |
||||
hostName: string; |
||||
hostCPUCores: number; |
||||
hostMemory: string; |
||||
}>; |
||||
anvilTotalCPUCores: number; |
||||
anvilTotalMemory: string; |
||||
servers: Array<{ |
||||
serverUUID: string; |
||||
serverName: string; |
||||
serverCPUCores: number; |
||||
serverMemory: string; |
||||
}>; |
||||
anvilTotalAllocatedCPUCores: number; |
||||
anvilTotalAllocatedMemory: string; |
||||
anvilTotalAvailableCPUCores: number; |
||||
anvilTotalAvailableMemory: string; |
||||
storageGroups: Array<{ |
||||
storageGroupUUID: string; |
||||
storageGroupName: string; |
||||
storageGroupSize: string; |
||||
storageGroupFree: string; |
||||
}>; |
||||
files: Array<{ |
||||
fileUUID: string; |
||||
fileName: string; |
||||
}>; |
||||
}; |
@ -0,0 +1,8 @@ |
||||
type AnvilOverview = { |
||||
anvilName: string; |
||||
anvilUUID: string; |
||||
hosts: Array<{ |
||||
hostName: string; |
||||
hostUUID: string; |
||||
}>; |
||||
}; |
@ -0,0 +1,3 @@ |
||||
type BuildGetRequestHandlerOptions = { |
||||
beforeRespond?: (queryStdout: unknown) => unknown; |
||||
}; |
@ -0,0 +1,9 @@ |
||||
type BuildQueryDetailOptions = { keys?: string[] | '*' }; |
||||
|
||||
type BuildQueryDetailReturn = { |
||||
query: string; |
||||
} & Pick<BuildQueryOptions, 'afterQueryReturn'>; |
||||
|
||||
type BuildQueryDetailFunction = ( |
||||
options?: BuildQueryDetailOptions, |
||||
) => BuildQueryDetailReturn; |
@ -0,0 +1,10 @@ |
||||
type QueryResultModifierFunction = (output: unknown) => unknown; |
||||
|
||||
type BuildQueryOptions = { |
||||
afterQueryReturn?: QueryResultModifierFunction; |
||||
}; |
||||
|
||||
type BuildQueryFunction = ( |
||||
request: import('express').Request, |
||||
options?: BuildQueryOptions, |
||||
) => string; |
@ -0,0 +1,4 @@ |
||||
type CallOptions = { |
||||
parameters?: unknown[]; |
||||
notCallableReturn?: unknown; |
||||
}; |
@ -0,0 +1,11 @@ |
||||
type CreateHostConnectionRequestBody = { |
||||
dbName?: string; |
||||
ipAddress: string; |
||||
isPing?: boolean; |
||||
// Host password; same as database password.
|
||||
password: string; |
||||
port?: number; |
||||
sshPort?: number; |
||||
// database user.
|
||||
user?: string; |
||||
}; |
@ -0,0 +1,9 @@ |
||||
type DBInsertOrUpdateFunctionCommonParams = ModuleSubroutineCommonParams & { |
||||
file: string; |
||||
line?: number; |
||||
}; |
||||
|
||||
type DBInsertOrUpdateFunctionCommonOptions = Omit< |
||||
ExecModuleSubroutineOptions, |
||||
'subParams' | 'subModuleName' |
||||
>; |
@ -0,0 +1,11 @@ |
||||
type DBJobParams = DBInsertOrUpdateFunctionCommonParams & { |
||||
job_command: string; |
||||
job_data?: string; |
||||
job_name: string; |
||||
job_title: string; |
||||
job_description: string; |
||||
job_host_uuid?: string; |
||||
job_progress?: number; |
||||
}; |
||||
|
||||
type DBInsertOrUpdateJobOptions = DBInsertOrUpdateFunctionCommonOptions; |
@ -0,0 +1,18 @@ |
||||
type DBVariableParams = DBInsertOrUpdateFunctionCommonParams & { |
||||
update_value_only?: 0 | 1; |
||||
variable_default?: string; |
||||
varaible_description?: string; |
||||
variable_name?: string; |
||||
variable_section?: string; |
||||
variable_source_table?: string; |
||||
variable_source_uuid?: string; |
||||
variable_uuid?: string; |
||||
variable_value?: number | string; |
||||
}; |
||||
|
||||
type DBInsertOrUpdateVariableOptions = DBInsertOrUpdateFunctionCommonOptions; |
||||
|
||||
type DBInsertOrUpdateVariableFunction = ( |
||||
subParams: DBVariableParams, |
||||
options?: DBInsertOrUpdateVariableOptions, |
||||
) => string; |
@ -0,0 +1,3 @@ |
||||
type DBJobAnvilSyncSharedOptions = { |
||||
jobHostUUID?: string; |
||||
}; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue