UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

284 lines 14.3 kB
// SPDX-License-Identifier: Apache-2.0 import { SoloError } from '../../errors/solo-error.js'; import { ComponentTypes } from './enumerations/component-types.js'; import { BaseStateSchema } from '../../../data/schema/model/remote/state/base-state-schema.js'; import { isValidEnum } from '../../util/validation-helpers.js'; import * as constants from '../../constants.js'; import { Templates } from '../../templates.js'; export class ComponentsDataWrapper { state; constructor(state) { this.state = state; } get componentIds() { return this.state.componentIds; } /* -------- Modifiers -------- */ /** Used to add new component to their respective group. */ addNewComponent(component, type, isReplace, skipIncrement = false) { const componentId = component.metadata.id; if (typeof componentId !== 'number') { throw new SoloError(`Component id is required ${componentId}`); } if (!(component instanceof BaseStateSchema)) { throw new SoloError('Component must be instance of BaseState', undefined, BaseStateSchema); } const addComponentCallback = (components) => { if (this.checkComponentExists(components, component) && !isReplace) { throw new SoloError('Component exists', undefined, component); } components.push(component); }; this.applyCallbackToComponentGroup(type, addComponentCallback, componentId); if (!skipIncrement) { // Increment the component id counter for the specified type when adding this.componentIds[type] += 1; } } // TODO: Remove once unified method is fully utilized changeNodePhase(componentId, phase) { if (!this.state.consensusNodes.some((component) => +component.metadata.id === +componentId)) { throw new SoloError(`Consensus node ${componentId} doesn't exist`); } const component = this.state.consensusNodes.find((component) => +component.metadata.id === +componentId); component.metadata.phase = phase; } changeComponentPhase(componentId, type, phase) { if (typeof componentId !== 'number') { throw new SoloError(`Component id is required ${componentId}`); } const updateComponentCallback = (components) => { const component = components.find((component) => component.metadata.id === componentId); if (!component) { throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to update`); } component.metadata.phase = phase; }; this.applyCallbackToComponentGroup(type, updateComponentCallback, componentId); } /** Used to remove specific component from their respective group. */ removeComponent(componentId, type) { if (typeof componentId !== 'number') { throw new SoloError(`Component id is required ${componentId}`); } if (!isValidEnum(type, ComponentTypes)) { throw new SoloError(`Invalid component type ${type}`); } const removeComponentCallback = (components) => { const index = components.findIndex((component) => component.metadata.id === componentId); if (index === -1) { throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to remove`); } components.splice(index, 1); }; this.applyCallbackToComponentGroup(type, removeComponentCallback, componentId); } /* -------- Utilities -------- */ getComponent(type, componentId) { let component; const getComponentCallback = (components) => { component = components.find((component) => component.metadata.id === componentId); if (!component) { throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to read`); } }; this.applyCallbackToComponentGroup(type, getComponentCallback, componentId); return component; } getComponentByType(type) { let components = []; const getComponentsByTypeCallback = (comps) => { components = comps; }; this.applyCallbackToComponentGroup(type, getComponentsByTypeCallback); return components; } getComponentsByClusterReference(type, clusterReference) { let filteredComponents = []; const getComponentsByClusterReferenceCallback = (components) => { filteredComponents = components.filter((component) => component.metadata.cluster === clusterReference); }; this.applyCallbackToComponentGroup(type, getComponentsByClusterReferenceCallback); return filteredComponents; } getComponentById(type, id) { let filteredComponent; const getComponentByIdCallback = (components) => { filteredComponent = components.find((component) => +component.metadata.id === +id); }; this.applyCallbackToComponentGroup(type, getComponentByIdCallback); if (!filteredComponent) { throw new SoloError(`Component of type ${type} with id ${id} was not found in remote config`); } return filteredComponent; } /** * Method used to map the type to the specific component group * and pass it to a callback to apply modifications */ applyCallbackToComponentGroup(componentType, callback, componentId) { switch (componentType) { case ComponentTypes.RelayNodes: { callback(this.state.relayNodes); break; } case ComponentTypes.HaProxy: { callback(this.state.haProxies); break; } case ComponentTypes.MirrorNode: { callback(this.state.mirrorNodes); break; } case ComponentTypes.EnvoyProxy: { callback(this.state.envoyProxies); break; } case ComponentTypes.ConsensusNode: { callback(this.state.consensusNodes); break; } case ComponentTypes.Explorer: { callback(this.state.explorers); break; } case ComponentTypes.BlockNode: { callback(this.state.blockNodes); break; } case ComponentTypes.Postgres: { callback(this.state.postgres); break; } case ComponentTypes.Redis: { callback(this.state.redis); break; } default: { throw new SoloError(`Unknown component type ${componentType}, component id: ${componentId}`); } } } /** checks if component exists in the respective group */ checkComponentExists(components, newComponent) { return components.some((component) => component.metadata.id === newComponent.metadata.id); } getNewComponentId(componentType) { return this.componentIds[componentType]; } /** * Manages port forwarding for a component, checking if it's already enabled and persisting configuration * @param clusterReference The cluster reference to forward to * @param podReference The pod reference to forward to * @param podPort The port on the pod to forward from * @param localPort The local port to forward to (starting port if not available) * @param k8Client The Kubernetes client to use for port forwarding * @param logger Logger for messages * @param componentType The component type for persistence * @param label Label for the port forward * @param reuse Whether to reuse existing port forward if available * @param nodeId Optional node ID for finding component when cluster reference is not available * @returns The local port number that was used for port forwarding */ async managePortForward(clusterReference, podReference, podPort, localPort, k8Client, logger, componentType, label, reuse = false, nodeId, persist = false, externalAddress) { // found component by cluster reference or nodeId let component; if (clusterReference) { const schemeComponents = this.getComponentsByClusterReference(componentType, clusterReference); component = schemeComponents[0]; } else { const componentId = Templates.renderComponentIdFromNodeId(nodeId); component = this.getComponentById(componentType, componentId); } if (component === undefined) { // it is possible we are upgrading a chart and previous version has no clusterReference save in configMap // so we will not be able to find component by clusterReference reuse = true; logger.showUser(`Port forward config not found for previous installed ${label}, reusing existing port forward`); } else if (component.metadata.portForwardConfigs) { for (const portForwardConfig of component.metadata.portForwardConfigs) { if (reuse === true && portForwardConfig.podPort === podPort) { if (portForwardConfig.localPort === localPort) { logger.showUser(`${label} Port forward already enabled at ${portForwardConfig.localPort}`); return portForwardConfig.localPort; } // localPort changed (migration) — kill the old process so portForward() reuse logic // does not find it and return the stale port, then remove the stale config. logger.showUser(`${label} Port forward migrating from ${portForwardConfig.localPort} to ${localPort}`); // eslint-disable-next-line unicorn/no-null await k8Client.pods().readByReference(null).stopPortForward(portForwardConfig.localPort); component.metadata.portForwardConfigs = component.metadata.portForwardConfigs.filter((c) => !(c.podPort === podPort && c.localPort === portForwardConfig.localPort)); break; } } } // Enable port forwarding const portForwardPortNumber = await k8Client .pods() .readByReference(podReference) .portForward(localPort, podPort, reuse, persist, externalAddress); logger.addMessageGroup(constants.PORT_FORWARDING_MESSAGE_GROUP, 'Port forwarding enabled'); logger.addMessageGroupMessage(constants.PORT_FORWARDING_MESSAGE_GROUP, `${label} port forward enabled on ${externalAddress || constants.LOCAL_HOST}:${portForwardPortNumber}`); if (component !== undefined) { component.metadata.portForwardConfigs ||= []; // Check if this exact podPort and localPort pair already exists const existingConfig = component.metadata.portForwardConfigs.find((config) => config.podPort === podPort && config.localPort === portForwardPortNumber); if (existingConfig) { logger.info(`port forward config already exists: localPort=${portForwardPortNumber}, podPort=${podPort}`); } else { logger.info(`add port localPort=${portForwardPortNumber}, podPort=${podPort}`); // Save port forward config to component component.metadata.portForwardConfigs.push({ podPort: podPort, localPort: portForwardPortNumber, }); } } return portForwardPortNumber; } /** * Stops port forwarding for a component by removing the configuration and stopping the forward * @param clusterReference The cluster reference * @param podReference The pod reference * @param podPort The port on the pod * @param localPort The local port * @param k8Client The Kubernetes client to use for stopping port forwarding * @param logger Logger for messages * @param componentType The component type * @param label Label for the port forward * @param nodeId Optional node ID for finding component when cluster reference is not available */ async stopPortForwards(clusterReference, podReference, podPort, localPort, k8Client, logger, componentType, label, nodeId) { // Find component by cluster reference or nodeId let component; if (clusterReference) { const schemeComponents = this.getComponentsByClusterReference(componentType, clusterReference); component = schemeComponents[0]; } else { const componentId = Templates.renderComponentIdFromNodeId(nodeId); component = this.getComponentById(componentType, componentId); } if (component === undefined || !component.metadata.portForwardConfigs) { logger.showUser(`No port forward config found for ${label}`); return; } // Find the matching port forward config const configIndex = component.metadata.portForwardConfigs.findIndex((config) => config.podPort === podPort && config.localPort === localPort); if (configIndex === -1) { logger.showUser(`Port forward config not found for ${label} with podPort=${podPort}, localPort=${localPort}`); return; } // Stop the port forward - use any pod reference since stopping should work regardless of pod // eslint-disable-next-line unicorn/no-null await k8Client.pods().readByReference(null).stopPortForward(localPort); logger.addMessageGroup(constants.PORT_FORWARDING_MESSAGE_GROUP, 'Port forwarding stopped'); logger.addMessageGroupMessage(constants.PORT_FORWARDING_MESSAGE_GROUP, `${label} port forward stopped on localhost:${localPort}`); // Remove the config from component metadata component.metadata.portForwardConfigs.splice(configIndex, 1); } } //# sourceMappingURL=components-data-wrapper.js.map