parent
b7b4e13028
commit
a50321eb9f
6 changed files with 241 additions and 1 deletions
@ -1,4 +1,5 @@ |
|||||||
export * from './getHostSSH'; |
export * from './getHostSSH'; |
||||||
export * from './poweroffHost'; |
export * from './poweroffHost'; |
||||||
export * from './rebootHost'; |
export * from './rebootHost'; |
||||||
|
export * from './runManifest'; |
||||||
export * from './updateSystem'; |
export * from './updateSystem'; |
||||||
|
@ -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<string, boolean | undefined> = {}; |
||||||
|
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<string, string> | undefined; |
||||||
|
|
||||||
|
try { |
||||||
|
anParams = Object.values(hostList).reduce<Record<string, string>>( |
||||||
|
(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(); |
||||||
|
}; |
Loading…
Reference in new issue