diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts new file mode 100644 index 00000000..ac6752b1 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts @@ -0,0 +1,250 @@ +import { RequestHandler } from 'express'; + +import { getAnvilData } from '../../accessModule'; +import { getEntityParts } from '../../disassembleEntityId'; +import { stderr, stdout } from '../../shell'; + +const handleSortEntries = ( + [aId]: T, + [bId]: T, +): number => { + const { name: at, number: an } = getEntityParts(aId); + const { name: bt, number: bn } = getEntityParts(bId); + + let result = 0; + + if (at === bt) { + if (an > bn) { + result = 1; + } else if (an < bn) { + result = -1; + } + } else if (at > bt) { + result = 1; + } else if (at < bt) { + result = -1; + } + + return result; +}; + +const handleSortNetworks = ( + [aId]: T, + [bId]: T, +): number => { + const isAIfn = /^ifn/.test(aId); + const isBIfn = /^ifn/.test(bId); + const { name: at, number: an } = getEntityParts(aId); + const { name: bt, number: bn } = getEntityParts(bId); + + let result = 0; + + if (at === bt) { + if (an > bn) { + result = 1; + } else if (an < bn) { + result = -1; + } + } else if (isAIfn) { + result = 1; + } else if (isBIfn) { + result = -1; + } else if (at > bt) { + result = 1; + } else if (at < bt) { + result = -1; + } + + return result; +}; + +export const getManifestDetail: RequestHandler = (request, response) => { + const { + params: { manifestUUID }, + } = request; + + let rawManifestListData: AnvilDataManifestListHash | undefined; + + try { + ({ manifests: rawManifestListData } = getAnvilData<{ + manifests?: AnvilDataManifestListHash; + }>( + { manifests: true }, + { + predata: [['Striker->load_manifest', { manifest_uuid: manifestUUID }]], + }, + )); + } catch (subError) { + stderr( + `Failed to get install manifest ${manifestUUID}; CAUSE: ${subError}`, + ); + + response.status(500).send(); + + return; + } + + stdout( + `Raw install manifest list:\n${JSON.stringify( + rawManifestListData, + null, + 2, + )}`, + ); + + if (!rawManifestListData) { + response.status(404).send(); + + return; + } + + const { + manifest_uuid: { + [manifestUUID]: { + parsed: { + domain, + fences: fenceUuidList = {}, + machine, + name, + networks: { dns: dnsCsv, mtu, name: networkList, ntp: ntpCsv }, + prefix, + sequence, + upses: upsUuidList = {}, + }, + }, + }, + } = rawManifestListData; + + const manifestData: ManifestDetail = { + domain, + hostConfig: { + hosts: Object.entries(machine) + .sort(handleSortEntries) + .reduce( + ( + previous, + [hostId, { fence = {}, ipmi_ip: ipmiIp, network, ups = {} }], + ) => { + const { name: hostType, number: hostNumber } = + getEntityParts(hostId); + + stdout(`host=${hostType},n=${hostNumber}`); + + previous[hostId] = { + fences: Object.entries(fence) + .sort(handleSortEntries) + .reduce( + (fences, [fenceName, { port: fencePort }]) => { + const fenceUuidContainer = fenceUuidList[fenceName]; + + if (fenceUuidContainer) { + const { uuid: fenceUuid } = fenceUuidContainer; + + fences[fenceName] = { + fenceName, + fencePort, + fenceUuid, + }; + } + + return fences; + }, + {}, + ), + hostNumber, + hostType, + ipmiIp, + networks: Object.entries(network) + .sort(handleSortNetworks) + .reduce( + (hostNetworks, [networkId, { ip: networkIp }]) => { + const { name: networkType, number: networkNumber } = + getEntityParts(networkId); + + stdout(`hostnetwork=${networkType},n=${networkNumber}`); + + hostNetworks[networkId] = { + networkIp, + networkNumber, + networkType, + }; + + return hostNetworks; + }, + {}, + ), + upses: Object.entries(ups) + .sort(handleSortEntries) + .reduce((upses, [upsName, { used }]) => { + const upsUuidContainer = upsUuidList[upsName]; + + if (upsUuidContainer) { + const { uuid: upsUuid } = upsUuidContainer; + + upses[upsName] = { + isUsed: Boolean(used), + upsName, + upsUuid, + }; + } + + return upses; + }, {}), + }; + + return previous; + }, + {}, + ), + }, + name, + networkConfig: { + dnsCsv, + mtu: Number.parseInt(mtu), + networks: Object.entries(networkList) + .sort(handleSortNetworks) + .reduce( + ( + networks, + [ + networkId, + { + gateway: networkGateway, + network: networkMinIp, + subnet: networkSubnetMask, + }, + ], + ) => { + const { name: networkType, number: networkNumber } = + getEntityParts(networkId); + + stdout(`network=${networkType},n=${networkNumber}`); + + networks[networkId] = { + networkGateway, + networkMinIp, + networkNumber, + networkSubnetMask, + networkType, + }; + + return networks; + }, + {}, + ), + ntpCsv, + }, + prefix, + sequence, + }; + + stdout( + `Extracted install manifest data:\n${JSON.stringify( + manifestData, + null, + 2, + )}`, + ); + + response.status(200).send(manifestData); +}; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/index.ts b/striker-ui-api/src/lib/request_handlers/manifest/index.ts index bf41d98e..93271202 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/index.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/index.ts @@ -1,2 +1,3 @@ export * from './getManifest'; +export * from './getManifestDetail'; export * from './getManifestTemplate'; diff --git a/striker-ui-api/src/routes/manifest.ts b/striker-ui-api/src/routes/manifest.ts index 6b3e5e5c..e68bca88 100644 --- a/striker-ui-api/src/routes/manifest.ts +++ b/striker-ui-api/src/routes/manifest.ts @@ -2,11 +2,15 @@ import express from 'express'; import { getManifest, + getManifestDetail, getManifestTemplate, } from '../lib/request_handlers/manifest'; const router = express.Router(); -router.get('/', getManifest).get('/template', getManifestTemplate); +router + .get('/', getManifest) + .get('/template', getManifestTemplate) + .get('/:manifestUUID', getManifestDetail); export default router; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index 0e846a90..b886ad0e 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -2,3 +2,72 @@ type ManifestOverview = { manifestName: string; manifestUUID: string; }; + +type ManifestDetailNetwork = { + networkGateway: string; + networkMinIp: string; + networkNumber: number; + networkSubnetMask: string; + networkType: string; +}; + +type ManifestDetailNetworkList = { + [networkId: string]: ManifestDetailNetwork; +}; + +type ManifestDetailFence = { + fenceName: string; + fencePort: string; + fenceUuid: string; +}; + +type ManifestDetailFenceList = { + [fenceId: string]: ManifestDetailFence; +}; + +type ManifestDetailHostNetwork = { + networkIp: string; + networkNumber: number; + networkType: string; +}; + +type ManifestDetailHostNetworkList = { + [networkId: string]: ManifestDetailHostNetwork; +}; + +type ManifestDetailUps = { + isUsed: boolean; + upsName: string; + upsUuid: string; +}; + +type ManifestDetailUpsList = { + [upsId: string]: ManifestDetailUps; +}; + +type ManifestDetailHostList = { + [hostId: string]: { + fences: ManifestDetailFenceList; + hostNumber: number; + hostType: string; + ipmiIp: string; + networks: ManifestDetailHostNetworkList; + upses: ManifestDetailUpsList; + }; +}; + +type ManifestDetail = { + domain: string; + hostConfig: { + hosts: ManifestDetailHostList; + }; + name: string; + networkConfig: { + dnsCsv: string; + mtu: number; + networks: ManifestDetailNetworkList; + ntpCsv: string; + }; + prefix: string; + sequence: string; +}; diff --git a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts index 191337b3..d82d05b6 100644 --- a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts +++ b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts @@ -13,6 +13,74 @@ type AnvilDataDatabaseHash = { }; }; +type AnvilDataManifestListHash = { + manifest_uuid: { + [manifestUUID: string]: { + parsed: { + domain: string; + fences?: { + [fenceId: string]: { + uuid: string; + }; + }; + machine: { + [hostId: string]: { + fence?: { + [fenceName: string]: { + port: string; + }; + }; + ipmi_ip: string; + name: string; + network: { + [networkId: string]: { + ip: string; + }; + }; + ups?: { + [upsName: string]: { + used: string; + }; + }; + }; + }; + name: string; + networks: { + count: { + [networkType: string]: number; + }; + dns: string; + mtu: string; + name: { + [networkId: string]: { + gateway: string; + network: string; + subnet: string; + }; + }; + ntp: string; + }; + prefix: string; + sequence: string; + upses?: { + [upsId: string]: { + uuid: string; + }; + }; + }; + }; + }; + name_to_uuid: Record; +} & Record< + string, + { + manifest_last_ran: number; + manifest_name: string; + manifest_note: string; + manifest_xml: string; + } +>; + type AnvilDataUPSHash = { [upsName: string]: { agent: string;