From a50321eb9fc024a5450c22201085313a199348cc Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 23 Mar 2023 16:26:47 -0400 Subject: [PATCH] fix(striker-ui-api): add command to run manifest --- striker-ui-api/src/lib/consts/SERVER_PATHS.ts | 1 + .../src/lib/request_handlers/command/index.ts | 1 + .../request_handlers/command/runManifest.ts | 193 ++++++++++++++++++ striker-ui-api/src/routes/command.ts | 2 + striker-ui-api/src/types/APIManifest.d.ts | 23 ++- .../src/types/GetAnvilDataFunction.d.ts | 22 ++ 6 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 striker-ui-api/src/lib/request_handlers/command/runManifest.ts diff --git a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts index f8cc8a2c..132d3015 100644 --- a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts +++ b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts @@ -19,6 +19,7 @@ const EMPTY_SERVER_PATHS: ServerPath = { 'anvil-access-module': {}, 'anvil-configure-host': {}, 'anvil-get-server-screenshot': {}, + 'anvil-join-anvil': {}, 'anvil-manage-keys': {}, 'anvil-manage-power': {}, 'anvil-provision-server': {}, diff --git a/striker-ui-api/src/lib/request_handlers/command/index.ts b/striker-ui-api/src/lib/request_handlers/command/index.ts index b42e11b0..2888de07 100644 --- a/striker-ui-api/src/lib/request_handlers/command/index.ts +++ b/striker-ui-api/src/lib/request_handlers/command/index.ts @@ -1,4 +1,5 @@ export * from './getHostSSH'; export * from './poweroffHost'; export * from './rebootHost'; +export * from './runManifest'; export * from './updateSystem'; diff --git a/striker-ui-api/src/lib/request_handlers/command/runManifest.ts b/striker-ui-api/src/lib/request_handlers/command/runManifest.ts new file mode 100644 index 00000000..a445e934 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/runManifest.ts @@ -0,0 +1,193 @@ +import assert from 'assert'; +import { RequestHandler } from 'express'; + +import { REP_PEACEFUL_STRING, REP_UUID } from '../../consts/REG_EXP_PATTERNS'; +import SERVER_PATHS from '../../consts/SERVER_PATHS'; + +import { getAnvilData, job, sub } from '../../accessModule'; +import { sanitize } from '../../sanitize'; +import { stderr } from '../../shell'; + +export const runManifest: RequestHandler< + { manifestUuid: string }, + undefined, + RunManifestRequestBody +> = (request, response) => { + const { + params: { manifestUuid }, + body: { + debug = 2, + description: rawDescription, + hosts: rawHostList = {}, + password: rawPassword, + } = {}, + } = request; + + const description = sanitize(rawDescription, 'string'); + const password = sanitize(rawPassword, 'string'); + + const hostList: ManifestExecutionHostList = {}; + + const handleAssertError = (assertError: unknown) => { + stderr( + `Failed to assert value when trying to run manifest ${manifestUuid}; CAUSE: ${assertError}`, + ); + + response.status(400).send(); + }; + + try { + assert( + REP_PEACEFUL_STRING.test(description), + `Description must be a peaceful string; got: [${description}]`, + ); + + assert( + REP_PEACEFUL_STRING.test(password), + `Password must be a peaceful string; got [${password}]`, + ); + + const uniqueList: Record = {}; + const isHostListUnique = !Object.values(rawHostList).some( + ({ hostNumber, hostType, hostUuid }) => { + const hostId = `${hostType}${hostNumber}`; + assert( + /^node[12]$/.test(hostId), + `Host ID must be "node" followed by 1 or 2; got [${hostId}]`, + ); + + assert( + REP_UUID.test(hostUuid), + `Host UUID assigned to ${hostId} must be a UUIDv4; got [${hostUuid}]`, + ); + + const isIdDuplicate = Boolean(uniqueList[hostId]); + const isUuidDuplicate = Boolean(uniqueList[hostUuid]); + + uniqueList[hostId] = true; + uniqueList[hostUuid] = true; + + hostList[hostId] = { hostNumber, hostType, hostUuid, hostId }; + + return isIdDuplicate || isUuidDuplicate; + }, + ); + + assert(isHostListUnique, `Each entry in hosts must be unique`); + } catch (assertError) { + handleAssertError(assertError); + + return; + } + + let rawHostListData: AnvilDataHostListHash | undefined; + let rawManifestListData: AnvilDataManifestListHash | undefined; + let rawSysData: AnvilDataSysHash | undefined; + + try { + ({ manifests: rawManifestListData, sys: rawSysData } = getAnvilData<{ + hosts?: AnvilDataHostListHash; + manifests?: AnvilDataManifestListHash; + sys?: AnvilDataSysHash; + }>( + { hosts: true, manifests: true, sys: true }, + { + predata: [ + ['Database->get_hosts'], + [ + 'Striker->load_manifest', + { + debug, + manifest_uuid: manifestUuid, + }, + ], + ], + }, + )); + } catch (subError) { + stderr( + `Failed to get install manifest ${manifestUuid}; CAUSE: ${subError}`, + ); + + response.status(500).send(); + + return; + } + + if (!rawHostListData || !rawManifestListData || !rawSysData) { + response.status(404).send(); + + return; + } + + const { host_uuid: hostUuidMapToData } = rawHostListData; + const { + manifest_uuid: { + [manifestUuid]: { + parsed: { name: manifestName }, + }, + }, + } = rawManifestListData; + const { hosts: { by_uuid: mapToHostNameData = {} } = {} } = rawSysData; + + const joinAnJobs: DBJobParams[] = []; + + let anParams: Record | undefined; + + try { + anParams = Object.values(hostList).reduce>( + (previous, { hostId = '', hostUuid }) => { + const hostName = mapToHostNameData[hostUuid]; + const { anvil_name: anName } = hostUuidMapToData[hostUuid]; + + assert( + anName && anName !== manifestName, + `Host ${hostName} cannot be used for ${manifestName} because it belongs to ${anName}`, + ); + + joinAnJobs.push({ + debug, + file: __filename, + job_command: SERVER_PATHS.usr.sbin['anvil-join-anvil'].self, + job_data: `as_machine=${hostId},manifest_uuid=${manifestUuid},anvil_uuid=`, + job_description: 'job_0073', + job_host_uuid: hostUuid, + job_name: `join_anvil::${hostId}`, + job_progress: 0, + job_title: 'job_0072', + }); + + previous[`anvil_${hostId}_host_uuid`] = hostUuid; + + return previous; + }, + { + anvil_description: description, + anvil_name: manifestName, + anvil_password: password, + }, + ); + } catch (assertError) { + handleAssertError(assertError); + + return; + } + + try { + const [newAnUuid] = sub('insert_or_update_anvils', { subParams: anParams }) + .stdout as [string]; + + joinAnJobs.forEach((jobParams) => { + jobParams.job_data += newAnUuid; + job(jobParams); + }); + } catch (subError) { + stderr(`Failed to record new anvil node entry; CAUSE: ${subError}`); + + response.status(500).send(); + + return; + } + + response.status(204).send(); +}; diff --git a/striker-ui-api/src/routes/command.ts b/striker-ui-api/src/routes/command.ts index 347c4f37..d547aba1 100644 --- a/striker-ui-api/src/routes/command.ts +++ b/striker-ui-api/src/routes/command.ts @@ -4,6 +4,7 @@ import { getHostSSH, poweroffHost, rebootHost, + runManifest, updateSystem, } from '../lib/request_handlers/command'; @@ -13,6 +14,7 @@ router .put('/inquire-host', getHostSSH) .put('/poweroff-host', poweroffHost) .put('/reboot-host', rebootHost) + .put('/run-manifest/:manifestUuid', runManifest) .put('/update-system', updateSystem); export default router; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index 08469aa4..fc1e5693 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -72,7 +72,19 @@ type ManifestDetail = { sequence: number; }; -type BuildManifestRequestBody = Omit; +type ManifestExecutionHost = { + anName?: string; + anUuid?: string; + hostId?: string; + hostName?: string; + hostNumber: number; + hostType: string; + hostUuid: string; +}; + +type ManifestExecutionHostList = { + [hostId: string]: ManifestExecutionHost; +}; type ManifestTemplate = { domain: string; @@ -91,3 +103,12 @@ type ManifestTemplate = { }; }; }; + +type BuildManifestRequestBody = Omit; + +type RunManifestRequestBody = { + debug?: number; + description: string; + hosts: ManifestExecutionHostList; + password: string; +}; diff --git a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts index d82d05b6..0d8c2a0c 100644 --- a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts +++ b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts @@ -13,6 +13,21 @@ type AnvilDataDatabaseHash = { }; }; +type AnvilDataHostListHash = { + host_uuid: { + [hostUuid: string]: { + anvil_name?: string; + anvil_uuid?: string; + host_ipmi: string; + host_key: string; + host_name: string; + host_status: string; + host_type: string; + short_host_name: string; + }; + }; +}; + type AnvilDataManifestListHash = { manifest_uuid: { [manifestUUID: string]: { @@ -81,6 +96,13 @@ type AnvilDataManifestListHash = { } >; +type AnvilDataSysHash = { + hosts?: { + by_uuid: { [hostUuid: string]: string }; + by_name: { [hostName: string]: string }; + }; +}; + type AnvilDataUPSHash = { [upsName: string]: { agent: string;