UNPKG

@stolostron/multicluster-sdk

Version:

Provides extensions and APIs that dynamic plugins can use to leverage multicluster capabilities provided by Red Hat Advanced Cluster Management.

385 lines 21.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertSearchItemToResource = convertSearchItemToResource; /* Copyright Contributors to the Open Cluster Management project */ const lodash_1 = require("lodash"); /** * Sets a value at the specified path on an object, but only if the value is defined. * @param obj - The object to modify * @param path - The path where the value should be set * @param value - The value to check for definedness * @param valueToSet - Optional alternative value to set instead of the original value */ const setIfDefined = (obj, path, value, valueToSet) => { if (value !== undefined) { (0, lodash_1.set)(obj, path, valueToSet ?? value); } }; /** * Sets conditions on an object if the object does not have any conditions. This covers older versions of search that did not have condition handling enabled for this resource type. * @param obj - The object to modify * @param conditions - An array of conditions to set */ const setFallbackConditions = (obj, conditions) => { if (!obj.status?.conditions) { const mappedConditions = conditions .filter((condition) => condition.value !== undefined) .map((condition) => ({ type: condition.type, status: condition.value, })); if (mappedConditions.length) { setIfDefined(obj, 'status.conditions', mappedConditions); } } }; /** * Converts a value to a boolean from an ACM search result, where properties are always strings. * @param value - The value to convert to a boolean * @returns The boolean value of the input value */ const convertToBoolean = (value) => { if (typeof value === 'boolean') { return value; } else if (typeof value === 'string') { return value.toLowerCase() === 'true'; } return false; }; /** * Generates a resource key from the kind and optional API group. * @param kind - The Kubernetes resource kind * @param apigroup - Optional API group * @returns A string key in the format "kind" or "kind.apigroup" */ const getResourceKey = (kind, apigroup) => { if (apigroup) { return `${kind}.${apigroup}`; } return kind; }; /** * Parses a condition string from search results into an array of condition objects. * @param conditionString - A semicolon-separated string of conditions in "type=status" format * @returns An array of condition objects with type and status, or undefined if invalid */ const parseConditionString = (conditionString) => { if (!conditionString || typeof conditionString !== 'string') { return undefined; } const conditions = conditionString .split(';') .filter((condition) => condition.includes('=')) .map((condition) => { const [type, status] = condition.split('='); return { type: type?.trim(), status: status?.trim(), }; }) .filter((condition) => condition.type && condition.status); return conditions.length > 0 ? conditions : undefined; }; /** * Parses a map string from search results into an object. * @param mapString - A semicolon-separated string of key-value pairs in "key=value" format * @returns An object with the key-value pairs, or undefined if the input falsy or not a string */ const parseMapString = (mapString) => { if (!mapString || typeof mapString !== 'string') { return undefined; } return Object.fromEntries(mapString.split(';').map((pair) => pair.trimStart().split('='))); }; /** * Parses a list string from search results into an array of strings. * @param listString - A semicolon-separated string of values * @returns An array with the values, or undefined if the input is falsy or not a string */ const parseListString = (listString) => { if (!listString || typeof listString !== 'string') { return undefined; } return listString .split(';') .map((value) => value.trim()) .filter(Boolean); }; /** * Gets the UUID from a search UUID string. * @param searchUUID - The search UUID string * @returns The UUID, or undefined if the input is falsy or not a string */ const getUUIDFromSearchUUID = (searchUUID) => { return searchUUID?.split('/').pop() || undefined; }; /** * Creates a resource object with the basic metadata available from search results. * @param item - The search result item * @returns A resource object with the appropriate nested fields */ const createResourceCommon = (item) => { const resource = { cluster: item.cluster, apiVersion: item.apigroup ? `${item.apigroup}/${item.apiversion}` : item.apiversion, kind: item.kind, metadata: { annotations: parseMapString(item.annotation), creationTimestamp: item.created, name: item.name, namespace: item.namespace, labels: parseMapString(item.label), uid: getUUIDFromSearchUUID(item._uid), // _uid field holds '<cluster>/<uid>' but may be removed in the future }, }; setIfDefined(resource, 'status.conditions', parseConditionString(item.condition)); return resource; }; /** * Converts a flattened search result item into a properly structured Kubernetes resource. * * This function reverses the flattening performed by the search-collector, reconstructing * the original resource structure from the flattened search data. * * @param item - The flattened search result item from the ACM search API * @returns A structured Kubernetes resource object with the appropriate nested fields * * @see https://github.com/stolostron/search-collector/blob/main/pkg/transforms/genericResourceConfig.go */ function convertSearchItemToResource(item) { const resource = createResourceCommon(item); // Reverse the flattening of specific resources by the search-collector const resourceKey = getResourceKey(item.kind, item.apigroup); // See https://github.com/stolostron/search-collector/blob/main/pkg/transforms/genericResourceConfig.go switch (resourceKey) { case 'ClusterServiceVersion.operators.coreos.com': setIfDefined(resource, 'spec.version', item.version); setIfDefined(resource, 'spec.displayName', item.display); setIfDefined(resource, 'status.phase', item.phase); break; case 'ClusterOperator.config.openshift.io': setIfDefined(resource, 'status.versions[0]', item.version, { name: 'operator', version: item.version }); setFallbackConditions(resource, [ { type: 'Available', value: item.available }, { type: 'Progressing', value: item.progressing }, { type: 'Degraded', value: item.degraded }, ]); break; case 'ConfigMap': setIfDefined(resource, 'data.spec.param.maxDesiredLatencyMilliseconds', item.configParamMaxDesiredLatency); setIfDefined(resource, 'data.spec.param.networkAttachmentDefinitionNamespace', item.configParamNADNamespace); setIfDefined(resource, 'data.spec.param.networkAttachmentDefinitionName', item.configParamNADName); setIfDefined(resource, 'data.spec.param.targetNode', item.configParamTargetNode); setIfDefined(resource, 'data.spec.param.sourceNode', item.configParamSourceNode); setIfDefined(resource, 'data.spec.param.sampleDurationSeconds', item.configParamSampleDuration); setIfDefined(resource, 'data.spec.timeout', item.configTimeout); setIfDefined(resource, 'data.status.completionTimestamp', item.configCompletionTimestamp); setIfDefined(resource, 'data.status.failureReason', item.configFailureReason); setIfDefined(resource, 'data.status.startTimestamp', item.configStartTimestamp); setIfDefined(resource, 'data.status.succeeded', item.configSucceeded); setIfDefined(resource, 'data.status.result.avgLatencyNanoSec', item.configStatusAVGLatencyNano); setIfDefined(resource, 'data.status.result.maxLatencyNanoSec', item.configStatusMaxLatencyNano); setIfDefined(resource, 'data.status.result.minLatencyNanoSec', item.configStatusMinLatencyNano); setIfDefined(resource, 'data.status.result.measurementDurationSec', item.configStatusMeasurementDuration); setIfDefined(resource, 'data.status.result.targetNode', item.configStatusTargetNode); setIfDefined(resource, 'data.status.result.sourceNode', item.configStatusSourceNode); break; case 'DataImportCron.cdi.kubevirt.io': setIfDefined(resource, 'spec.managedDataSource', item.managedDataSource); break; case 'DataSource.cdi.kubevirt.io': setIfDefined(resource, 'spec.source.pvc.name', item.pvcName); setIfDefined(resource, 'spec.source.pvc.namespace', item.pvcNamespace); setIfDefined(resource, 'spec.source.snapshot.name', item.snapshotName); setIfDefined(resource, 'spec.source.snapshot.namespace', item.snapshotNamespace); break; case 'DataVolume.cdi.kubevirt.io': setIfDefined(resource, 'spec.source.pvc.name', item.pvcName); setIfDefined(resource, 'spec.source.pvc.namespace', item.pvcNamespace); setIfDefined(resource, 'spec.source.snapshot.name', item.snapshotName); setIfDefined(resource, 'spec.source.snapshot.namespace', item.snapshotNamespace); setIfDefined(resource, 'spec.storage.resources.requests.storage', item.size); setIfDefined(resource, 'spec.storage.storageClassName', item.storageClassName); setIfDefined(resource, 'status.phase', item.phase); break; case 'MigrationPolicy.migrations.kubevirt.io': setIfDefined(resource, 'spec.allowAutoConverge', item.allowAutoConverge, convertToBoolean(item.allowAutoConverge)); setIfDefined(resource, 'spec.allowPostCopy', item.allowPostCopy, convertToBoolean(item.allowPostCopy)); setIfDefined(resource, 'spec.bandwidthPerMigration', item.bandwidthPerMigration, Number(item.bandwidthPerMigration)); setIfDefined(resource, 'spec.completionTimeoutPerGiB', item.completionTimeoutPerGiB, Number(item.completionTimeoutPerGiB)); setIfDefined(resource, 'spec.selectors.namespaceSelector', parseMapString(item._namespaceSelector)); setIfDefined(resource, 'spec.selectors.virtualMachineInstanceSelector', parseMapString(item._virtualMachineInstanceSelector)); break; case 'Namespace': setIfDefined(resource, 'status.phase', item.status); break; case 'Node': setIfDefined(resource, 'status.addresses[0]', item.ipAddress, { type: 'InternalIP', address: item.ipAddress, }); setIfDefined(resource, 'status.allocatable.memory', item.memoryAllocatable); setIfDefined(resource, 'status.capacity.memory', item.memoryCapacity); setIfDefined(resource, 'status.nodeInfo.architecture', item.architecture); break; case 'PersistentVolumeClaim': setIfDefined(resource, 'spec.resources.requests.storage', item.requestedStorage); setIfDefined(resource, 'spec.storageClassName', item.storageClassName); setIfDefined(resource, 'spec.volumeMode', item.volumeMode); setIfDefined(resource, 'status.phase', item.status); setIfDefined(resource, 'status.capacity.storage', item.capacity); break; case 'Pod': { setIfDefined(resource, 'metadata.ownerReferences', item._ownerUID, [ { uid: getUUIDFromSearchUUID(item._ownerUID) }, ]); const initContainerNamesList = parseListString(item.initContainer); setIfDefined(resource, 'spec.initContainers', initContainerNamesList, initContainerNamesList?.map((name) => ({ name }))); break; } case 'StorageClass.storage.k8s.io': setIfDefined(resource, 'allowVolumeExpansion', item.allowVolumeExpansion); setIfDefined(resource, 'provisioner', item.provisioner); setIfDefined(resource, 'reclaimPolicy', item.reclaimPolicy); setIfDefined(resource, 'volumeBindingMode', item.volumeBindingMode); break; case 'Subscription.operators.coreos.com': setIfDefined(resource, 'spec.source', item.source); setIfDefined(resource, 'spec.name', item.package); setIfDefined(resource, 'spec.channel', item.channel); setIfDefined(resource, 'status.installedCSV', item.installplan); setIfDefined(resource, 'status.state', item.phase); break; case 'VirtualMachine.kubevirt.io': { setIfDefined(resource, 'spec.instancetype.name', item.instancetype); setIfDefined(resource, 'spec.preference.name', item.preference); setIfDefined(resource, 'spec.runStrategy', item.runStrategy); setIfDefined(resource, 'spec.template.spec.architecture', item.architecture); setIfDefined(resource, 'spec.template.spec.domain.cpu.cores', item.cpu, Number(item.cpu)); setIfDefined(resource, 'spec.template.spec.domain.memory.guest', item.memory); setIfDefined(resource, 'spec.template.metadata.annotations["vm.kubevirt.io/flavor"]', item.flavor); setIfDefined(resource, 'spec.template.metadata.annotations["vm.kubevirt.io/os"]', item.osName); setIfDefined(resource, 'spec.template.metadata.annotations["vm.kubevirt.io/workload"]', item.workload); const dataVolumeNamesList = parseListString(item.dataVolumeNames); const pvcClaimNamesList = parseListString(item.pvcClaimNames); const volumes = dataVolumeNamesList || pvcClaimNamesList ? [ ...(dataVolumeNamesList || []).map((name) => ({ dataVolume: { name }, })), ...(pvcClaimNamesList || []).map((claimName) => ({ persistentVolumeClaim: { claimName }, })), ] : undefined; setIfDefined(resource, 'spec.template.spec.volumes', volumes); setIfDefined(resource, 'status.printableStatus', item.status); setFallbackConditions(resource, [ { type: 'Ready', value: item.ready }, { type: 'AgentConnected', value: item.agentConnected }, ]); break; } case 'VirtualMachineClone.clone.kubevirt.io': setIfDefined(resource, 'spec.source.name', item.sourceName); setIfDefined(resource, 'spec.source.kind', item.sourceKind); setIfDefined(resource, 'spec.target.name', item.targetName); setIfDefined(resource, 'spec.target.kind', item.targetKind); setIfDefined(resource, 'status.phase', item.phase); break; case 'VirtualMachineInstance.kubevirt.io': { setIfDefined(resource, 'spec.domain.cpu.cores', item.cpu, Number(item.cpu)); setIfDefined(resource, 'spec.domain.cpu.sockets', item.cpuSockets, Number(item.cpuSockets)); setIfDefined(resource, 'spec.domain.cpu.threads', item.cpuThreads, Number(item.cpuThreads)); setIfDefined(resource, 'spec.domain.devices.gpus', parseListString(item.gpuName)?.map((name) => ({ name }))); setIfDefined(resource, 'spec.domain.devices.hostDevices', parseListString(item.hostDeviceName)?.map((name) => ({ name }))); setIfDefined(resource, 'spec.domain.devices.interfaces', parseListString(item.interfaceName)?.map((name) => ({ name }))); setIfDefined(resource, 'spec.domain.memory.guest', item.memory); const interfaces = []; if (item._interface) { // _interfaces has format: "<name1>/<interfaceName1>[0]=<ipAddress>; <name1>/<interfaceName2>[1]=<ipAddress>; <name2>/interfaceName2>[0]=<ipAddress>" // The name (optional) and interface are separated by a slash, followed by the index of the ipAddress in the ipAddresses array const interfaceMap = new Map(); const keyIndexRE = /^(?<name>[^/]*)?\/(?<interfaceName>[^/]*)?\[(?<index>\d+)\]$/; parseListString(item._interface)?.forEach((interfaceString) => { const [keyIndex, ipAddress] = interfaceString.split('='); const match = keyIndexRE.exec(keyIndex); if (match) { const name = match.groups?.name ?? ''; const interfaceName = match.groups?.interfaceName ?? ''; const index = Number(match.groups?.index ?? 0); const key = `${name}/${interfaceName}`; const interfaceObject = interfaceMap.get(key) || interfaceMap .set(key, { ...(name && { name }), ...(interfaceName && { interfaceName }), ipAddresses: [], }) .get(key); if (index === 0) { interfaceObject.ipAddress = ipAddress; } interfaceObject.ipAddresses[index] = ipAddress; } }); interfaces.push(...interfaceMap.values()); } else if (item.ipaddress) { interfaces.push({ ipAddress: item.ipaddress, name: 'default', }); } setIfDefined(resource, 'status.interfaces', interfaces.length ? interfaces : undefined); setIfDefined(resource, 'status.nodeName', item.node); setIfDefined(resource, 'status.phase', item.phase); setIfDefined(resource, 'status.guestOSInfo.version', item.osVersion); setFallbackConditions(resource, [ { type: 'LiveMigratable', value: item.liveMigratable }, { type: 'Ready', value: item.ready }, ]); break; } case 'VirtualMachineInstanceMigration.kubevirt.io': setIfDefined(resource, 'metadata.deletionTimestamp', item.deleted); setIfDefined(resource, 'status.migrationState.endTimestamp', item.endTime); setIfDefined(resource, 'status.migrationState.migrationPolicyName', item.migrationPolicyName); setIfDefined(resource, 'status.migrationState.sourceNode', item.sourceNode); setIfDefined(resource, 'status.migrationState.sourcePod', item.sourcePod); setIfDefined(resource, 'status.migrationState.targetNode', item.targetNode); setIfDefined(resource, 'status.phase', item.phase); setIfDefined(resource, 'spec.vmiName', item.vmiName); break; case 'VirtualMachineInstancetype.instancetype.kubevirt.io': case 'VirtualMachineClusterInstancetype.instancetype.kubevirt.io': setIfDefined(resource, 'spec.cpu.guest', item.cpuGuest, Number(item.cpuGuest)); setIfDefined(resource, 'spec.memory.guest', item.memoryGuest, Number(item.memoryGuest)); break; case 'VirtualMachineSnapshot.snapshot.kubevirt.io': setIfDefined(resource, 'status.phase', item.phase); setIfDefined(resource, 'status.indications', parseListString(item.indications)); setIfDefined(resource, 'spec.source.kind', item.sourceKind); setIfDefined(resource, 'spec.source.name', item.sourceName); setIfDefined(resource, 'status.readyToUse', item.readyToUse, convertToBoolean(item.readyToUse)); setFallbackConditions(resource, [{ type: 'Ready', value: item.ready }]); break; case 'VirtualMachineRestore.snapshot.kubevirt.io': setIfDefined(resource, 'status.restoreTime', item.restoreTime); setIfDefined(resource, 'status.complete', item.complete); setIfDefined(resource, 'spec.target.apiGroup', item.targetApiGroup); setIfDefined(resource, 'spec.target.kind', item.targetKind); setIfDefined(resource, 'spec.target.name', item.targetName); setIfDefined(resource, 'spec.virtualMachineSnapshotName', item.virtualMachineSnapshotName); setFallbackConditions(resource, [{ type: 'Ready', value: item.ready }]); break; case 'VolumeSnapshot.snapshot.storage.k8s.io': setIfDefined(resource, 'spec.volumeSnapshotClassName', item.volumeSnapshotClassName); setIfDefined(resource, 'spec.source.persistentVolumeClaimName', item.persistentVolumeClaimName); setIfDefined(resource, 'status.restoreSize', item.restoreSize); break; } return resource; } //# sourceMappingURL=convertSearchItemToResource.js.map