From abd2780d02a51663ebc640b3b2af726e5e3297ae Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 4 Oct 2023 05:09:40 -0400 Subject: [PATCH] fix(striker-ui-api): allow create manifest with no fences or UPSes --- .../manifest/buildManifest.ts | 350 ++++++++++-------- .../manifest/createManifest.ts | 6 +- .../manifest/updateManifest.ts | 6 +- striker-ui-api/src/lib/shell.ts | 10 +- 4 files changed, 203 insertions(+), 169 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts index 60d16ddc..6e5b7a35 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts @@ -2,7 +2,6 @@ import assert from 'assert'; import { RequestHandler } from 'express'; import { - REP_INTEGER, REP_IPV4, REP_IPV4_CSV, REP_PEACEFUL_STRING, @@ -41,221 +40,248 @@ export const buildManifest = async ( stdout('Begin building install manifest.'); const dns = sanitize(rawDns, 'string'); - assert(REP_IPV4_CSV.test(dns), `DNS must be an IPv4 CSV; got [${dns}]`); - const domain = sanitize(rawDomain, 'string'); - assert( - REP_PEACEFUL_STRING.test(domain), - `Domain must be a peaceful string; got [${domain}]`, - ); - const manifestUuid = sanitize(rawManifestUuid, 'string'); - assert( - manifestUuid === 'new' || REP_UUID.test(manifestUuid), - `Manifest UUID must be a UUIDv4; got [${manifestUuid}]`, - ); - const mtu = sanitize(rawMtu, 'number'); - assert(REP_INTEGER.test(String(mtu)), `MTU must be an integer; got [${mtu}]`); - const ntp = sanitize(rawNtp, 'string'); + const prefix = sanitize(rawPrefix, 'string'); + const sequence = sanitize(rawSequence, 'number'); - if (ntp) { - assert(REP_IPV4_CSV.test(ntp), `NTP must be an IPv4 CSV; got [${ntp}]`); - } + try { + assert(REP_IPV4_CSV.test(dns), `DNS must be an IPv4 CSV; got [${dns}]`); - const prefix = sanitize(rawPrefix, 'string'); - assert( - REP_PEACEFUL_STRING.test(prefix), - `Prefix must be a peaceful string; got [${prefix}]`, - ); + assert( + REP_PEACEFUL_STRING.test(domain), + `Domain must be a peaceful string; got [${domain}]`, + ); - const sequence = sanitize(rawSequence, 'number'); - assert( - REP_INTEGER.test(String(sequence)), - `Sequence must be an integer; got [${sequence}]`, - ); - - const { counts: networkCountContainer, networks: networkContainer } = - Object.values(networkList).reduce<{ - counts: Record; - networks: Record; - }>( - ( - previous, - { - networkGateway: rawGateway, - networkMinIp: rawMinIp, - networkNumber: rawNetworkNumber, - networkSubnetMask: rawSubnetMask, - networkType: rawNetworkType, - }, - ) => { - const networkType = sanitize(rawNetworkType, 'string'); - assert( - REP_PEACEFUL_STRING.test(networkType), - `Network type must be a peaceful string; got [${networkType}]`, - ); + assert( + manifestUuid === 'new' || REP_UUID.test(manifestUuid), + `Manifest UUID must be a UUIDv4; got [${manifestUuid}]`, + ); - const networkNumber = sanitize(rawNetworkNumber, 'number'); - assert( - REP_INTEGER.test(String(networkNumber)), - `Network number must be an integer; got [${networkNumber}]`, - ); + assert(Number.isSafeInteger(mtu), `MTU must be an integer; got [${mtu}]`); - const networkId = `${networkType}${networkNumber}`; + if (ntp) { + assert(REP_IPV4_CSV.test(ntp), `NTP must be an IPv4 CSV; got [${ntp}]`); + } - const gateway = sanitize(rawGateway, 'string'); + assert( + REP_PEACEFUL_STRING.test(prefix), + `Prefix must be a peaceful string; got [${prefix}]`, + ); - if (networkType === 'ifn') { - assert( - REP_IPV4.test(gateway), - `Gateway of ${networkId} must be an IPv4; got [${gateway}]`, - ); - } + assert( + Number.isSafeInteger(sequence), + `Sequence must be an integer; got [${sequence}]`, + ); + } catch (error) { + throw new Error(`Failed to assert build manifest input; CAUSE: ${error}`); + } - const minIp = sanitize(rawMinIp, 'string'); - assert( - REP_IPV4.test(minIp), - `Minimum IP of ${networkId} must be an IPv4; got [${minIp}]`, - ); + const netCounts: Record = {}; + const netConfigs: Record = {}; + + try { + Object.values(networkList).forEach((network) => { + const { + networkGateway: rawGateway, + networkMinIp: rawMinIp, + networkNumber: rawNetworkNumber, + networkSubnetMask: rawSubnetMask, + networkType: rawNetworkType, + } = network; + + const gateway = sanitize(rawGateway, 'string'); + const minIp = sanitize(rawMinIp, 'string'); + const networkNumber = sanitize(rawNetworkNumber, 'number'); + const networkType = sanitize(rawNetworkType, 'string'); + const subnetMask = sanitize(rawSubnetMask, 'string'); + + const networkId = `${networkType}${networkNumber}`; - const subnetMask = sanitize(rawSubnetMask, 'string'); + assert( + REP_PEACEFUL_STRING.test(networkType), + `Network type must be a peaceful string; got [${networkType}]`, + ); + + assert( + Number.isSafeInteger(networkNumber), + `Network number must be an integer; got [${networkNumber}]`, + ); + + assert( + REP_IPV4.test(minIp), + `Minimum IP of ${networkId} must be an IPv4; got [${minIp}]`, + ); + + assert( + REP_IPV4.test(subnetMask), + `Subnet mask of ${networkId} must be an IPv4; got [${subnetMask}]`, + ); + + if (networkType === 'ifn') { assert( - REP_IPV4.test(subnetMask), - `Subnet mask of ${networkId} must be an IPv4; got [${subnetMask}]`, + REP_IPV4.test(gateway), + `Gateway of ${networkId} must be an IPv4; got [${gateway}]`, ); + } - const { counts: countContainer, networks: networkContainer } = previous; - - const countKey = `${networkType}_count`; - const countValue = countContainer[countKey] ?? 0; + const countKey = `${networkType}_count`; + const countValue = netCounts[countKey] ?? 0; - countContainer[countKey] = countValue + 1; + netCounts[countKey] = countValue + 1; - const gatewayKey = `${networkId}_gateway`; - const minIpKey = `${networkId}_network`; - const subnetMaskKey = `${networkId}_subnet`; + const gatewayKey = `${networkId}_gateway`; + const minIpKey = `${networkId}_network`; + const subnetMaskKey = `${networkId}_subnet`; - networkContainer[gatewayKey] = gateway; - networkContainer[minIpKey] = minIp; - networkContainer[subnetMaskKey] = subnetMask; + netConfigs[gatewayKey] = gateway; + netConfigs[minIpKey] = minIp; + netConfigs[subnetMaskKey] = subnetMask; + }); + } catch (error) { + throw new Error(`Failed to build networks for manifest; CAUSE: ${error}`); + } - return previous; - }, - { counts: {}, networks: {} }, - ); + const hosts: Record = {}; - const hostContainer = Object.values(hostList).reduce>( - ( - previous, - { + try { + Object.values(hostList).forEach((host) => { + const { fences, hostNumber: rawHostNumber, hostType: rawHostType, ipmiIp: rawIpmiIp, networks, upses, - }, - ) => { + } = host; + + const hostNumber = sanitize(rawHostNumber, 'number'); const hostType = sanitize(rawHostType, 'string'); + const ipmiIp = sanitize(rawIpmiIp, 'string'); + + const hostId = `${hostType}${hostNumber}`; + assert( REP_PEACEFUL_STRING.test(hostType), `Host type must be a peaceful string; got [${hostType}]`, ); - const hostNumber = sanitize(rawHostNumber, 'number'); assert( - REP_INTEGER.test(String(hostNumber)), + Number.isSafeInteger(hostNumber), `Host number must be an integer; got [${hostNumber}]`, ); - const hostId = `${hostType}${hostNumber}`; - - const ipmiIp = sanitize(rawIpmiIp, 'string'); assert( REP_IPV4.test(ipmiIp), `IPMI IP of ${hostId} must be an IPv4; got [${ipmiIp}]`, ); + assert.ok(networks, `Host networks is required`); + const ipmiIpKey = `${hostId}_ipmi_ip`; - previous[ipmiIpKey] = ipmiIp; - - Object.values(networks).forEach( - ({ - networkIp: rawIp, - networkNumber: rawNetworkNumber, - networkType: rawNetworkType, - }) => { - const networkType = sanitize(rawNetworkType, 'string'); - assert( - REP_PEACEFUL_STRING.test(networkType), - `Network type must be a peaceful string; got [${networkType}]`, - ); + hosts[ipmiIpKey] = ipmiIp; - const networkNumber = sanitize(rawNetworkNumber, 'number'); - assert( - REP_INTEGER.test(String(networkNumber)), - `Network number must be an integer; got [${networkNumber}]`, - ); + try { + Object.values(networks).forEach( + ({ + networkIp: rawIp, + networkNumber: rawNetworkNumber, + networkType: rawNetworkType, + }) => { + const ip = sanitize(rawIp, 'string'); + const networkNumber = sanitize(rawNetworkNumber, 'number'); + const networkType = sanitize(rawNetworkType, 'string'); - const networkId = `${networkType}${networkNumber}`; + const networkId = `${networkType}${networkNumber}`; - const ip = sanitize(rawIp, 'string'); - assert( - REP_IPV4.test(ip), - `IP of host network ${networkId} must be an IPv4; got [${ip}]`, - ); + assert( + REP_PEACEFUL_STRING.test(networkType), + `Network type must be a peaceful string; got [${networkType}]`, + ); - const networkIpKey = `${hostId}_${networkId}_ip`; + assert( + Number.isSafeInteger(networkNumber), + `Network number must be an integer; got [${networkNumber}]`, + ); - previous[networkIpKey] = ip; - }, - ); + assert( + REP_IPV4.test(ip), + `IP of host network ${networkId} must be an IPv4; got [${ip}]`, + ); - Object.values(fences).forEach( - ({ fenceName: rawFenceName, fencePort: rawPort }) => { - const fenceName = sanitize(rawFenceName, 'string'); - assert( - REP_PEACEFUL_STRING.test(fenceName), - `Fence name must be a peaceful string; got [${fenceName}]`, - ); + const networkIpKey = `${hostId}_${networkId}_ip`; - const fenceKey = `${hostId}_fence_${fenceName}`; + hosts[networkIpKey] = ip; + }, + ); + } catch (error) { + throw new Error( + `Failed to build [${hostId}] networks for manifest; CAUSE: ${error}`, + ); + } - const port = sanitize(rawPort, 'string'); - assert( - REP_PEACEFUL_STRING.test(port), - `Port of ${fenceName} must be a peaceful string; got [${port}]`, - ); + try { + if (fences) { + Object.values(fences).forEach( + ({ fenceName: rawFenceName, fencePort: rawPort }) => { + const fenceName = sanitize(rawFenceName, 'string'); + const port = sanitize(rawPort, 'string'); - previous[fenceKey] = port; - }, - ); + assert( + REP_PEACEFUL_STRING.test(fenceName), + `Fence name must be a peaceful string; got [${fenceName}]`, + ); + + assert( + REP_PEACEFUL_STRING.test(port), + `Port of ${fenceName} must be a peaceful string; got [${port}]`, + ); - Object.values(upses).forEach( - ({ isUsed: rawIsUsed, upsName: rawUpsName }) => { - const upsName = sanitize(rawUpsName, 'string'); - assert( - REP_PEACEFUL_STRING.test(upsName), - `UPS name must be a peaceful string; got [${upsName}]`, + const fenceKey = `${hostId}_fence_${fenceName}`; + + hosts[fenceKey] = port; + }, ); + } + } catch (error) { + throw new Error( + `Failed to build [${hostId}] fences for manifest; CAUSE: ${error}`, + ); + } - const upsKey = `${hostId}_ups_${upsName}`; + try { + if (upses) { + Object.values(upses).forEach( + ({ isUsed: rawIsUsed, upsName: rawUpsName }) => { + const upsName = sanitize(rawUpsName, 'string'); - const isUsed = sanitize(rawIsUsed, 'boolean'); + assert( + REP_PEACEFUL_STRING.test(upsName), + `UPS name must be a peaceful string; got [${upsName}]`, + ); - if (isUsed) { - previous[upsKey] = 'checked'; - } - }, - ); + const upsKey = `${hostId}_ups_${upsName}`; + + const isUsed = sanitize(rawIsUsed, 'boolean'); - return previous; - }, - {}, - ); + if (isUsed) { + hosts[upsKey] = 'checked'; + } + }, + ); + } + } catch (error) { + throw new Error( + `Failed to build ${hostId} UPSes for manifest; CAUSE: ${error}`, + ); + } + }); + } catch (error) { + throw new Error(`Failed to build hosts for manifest; CAUSE: ${error}`); + } let result: { name: string; uuid: string }; @@ -272,9 +298,9 @@ export const buildManifest = async ( ntp, prefix, sequence, - ...networkCountContainer, - ...networkContainer, - ...hostContainer, + ...netCounts, + ...netConfigs, + ...hosts, }, ], pre: ['Striker'], @@ -282,8 +308,8 @@ export const buildManifest = async ( ); result = { name, uuid }; - } catch (subError) { - throw new Error(`Failed to generate manifest; CAUSE: ${subError}`); + } catch (error) { + throw new Error(`Failed to generate manifest; CAUSE: ${error}`); } return result; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts index 292e249f..ca65849c 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts @@ -11,12 +11,12 @@ export const createManifest: RequestHandler = async (...handlerArgs) => { try { result = await buildManifest(...handlerArgs); - } catch (buildError) { - stderr(`Failed to create new install manifest; CAUSE ${buildError}`); + } catch (error) { + stderr(`Failed to create new install manifest; CAUSE ${error}`); let code = 500; - if (buildError instanceof AssertionError) { + if (error instanceof AssertionError) { code = 400; } diff --git a/striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts index 9d583ac6..1b482d21 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts @@ -14,14 +14,14 @@ export const updateManifest: RequestHandler = async (...args) => { try { result = await buildManifest(...args); - } catch (buildError) { + } catch (error) { stderr( - `Failed to update install manifest ${manifestUuid}; CAUSE: ${buildError}`, + `Failed to update install manifest ${manifestUuid}; CAUSE: ${error}`, ); let code = 500; - if (buildError instanceof AssertionError) { + if (error instanceof AssertionError) { code = 400; } diff --git a/striker-ui-api/src/lib/shell.ts b/striker-ui-api/src/lib/shell.ts index e0a78847..72c51fe9 100644 --- a/striker-ui-api/src/lib/shell.ts +++ b/striker-ui-api/src/lib/shell.ts @@ -54,7 +54,15 @@ export const resolveGid = (id: number | string) => resolveId(id, 'group'); export const resolveUid = (id: number | string) => resolveId(id, 'passwd'); -export const stderr = (message: string) => print(message, { stream: 'stderr' }); +export const stderr = (message: string, error?: unknown) => { + let msg = message; + + if (error instanceof Error) { + msg += `\n${error.stack}`; + } + + print(msg, { stream: 'stderr' }); +}; export const stdout = (message: string) => print(message);