|
|
|
@ -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<string, number>; |
|
|
|
|
networks: Record<string, string>; |
|
|
|
|
}>( |
|
|
|
|
( |
|
|
|
|
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<string, number> = {}; |
|
|
|
|
const netConfigs: Record<string, string> = {}; |
|
|
|
|
|
|
|
|
|
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<string, string> = {}; |
|
|
|
|
|
|
|
|
|
const hostContainer = Object.values(hostList).reduce<Record<string, string>>( |
|
|
|
|
( |
|
|
|
|
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; |
|
|
|
|