@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
243 lines • 11.3 kB
JavaScript
/**
* SPDX-License-Identifier: Apache-2.0
*/
import { ComponentType, ConsensusNodeStates } from './enumerations.js';
import { SoloError } from '../../errors.js';
import { BaseComponent } from './components/base_component.js';
import { RelayComponent } from './components/relay_component.js';
import { HaProxyComponent } from './components/ha_proxy_component.js';
import { MirrorNodeComponent } from './components/mirror_node_component.js';
import { EnvoyProxyComponent } from './components/envoy_proxy_component.js';
import { ConsensusNodeComponent } from './components/consensus_node_component.js';
import { MirrorNodeExplorerComponent } from './components/mirror_node_explorer_component.js';
/**
* Represent the components in the remote config and handles:
* - CRUD operations on the components.
* - Validation.
* - Conversion FROM and TO plain object.
*/
export class ComponentsDataWrapper {
relays;
haProxies;
mirrorNodes;
envoyProxies;
consensusNodes;
mirrorNodeExplorers;
/**
* @param relays - Relay record mapping service name to relay components
* @param haProxies - HA Proxies record mapping service name to ha proxies components
* @param mirrorNodes - Mirror Nodes record mapping service name to mirror nodes components
* @param envoyProxies - Envoy Proxies record mapping service name to envoy proxies components
* @param consensusNodes - Consensus Nodes record mapping service name to consensus nodes components
* @param mirrorNodeExplorers - Mirror Node Explorers record mapping service name to mirror node explorers components
*/
constructor(relays = {}, haProxies = {}, mirrorNodes = {}, envoyProxies = {}, consensusNodes = {}, mirrorNodeExplorers = {}) {
this.relays = relays;
this.haProxies = haProxies;
this.mirrorNodes = mirrorNodes;
this.envoyProxies = envoyProxies;
this.consensusNodes = consensusNodes;
this.mirrorNodeExplorers = mirrorNodeExplorers;
this.validate();
}
/* -------- Modifiers -------- */
/** Used to add new component to their respective group. */
add(serviceName, component) {
const self = this;
if (!serviceName || typeof serviceName !== 'string') {
throw new SoloError(`Service name is required ${serviceName}`);
}
if (!(component instanceof BaseComponent)) {
throw new SoloError('Component must be instance of BaseComponent', null, BaseComponent);
}
function addComponentCallback(components) {
if (self.exists(components, component)) {
throw new SoloError('Component exists', null, component.toObject());
}
components[serviceName] = component;
}
self.applyCallbackToComponentGroup(component.type, serviceName, addComponentCallback);
}
/** Used to edit an existing component from their respective group. */
edit(serviceName, component) {
const self = this;
if (!serviceName || typeof serviceName !== 'string') {
throw new SoloError(`Service name is required ${serviceName}`);
}
if (!(component instanceof BaseComponent)) {
throw new SoloError('Component must be instance of BaseComponent', null, BaseComponent);
}
function editComponentCallback(components) {
if (!components[serviceName]) {
throw new SoloError(`Component doesn't exist, name: ${serviceName}`, null, { component });
}
components[serviceName] = component;
}
self.applyCallbackToComponentGroup(component.type, serviceName, editComponentCallback);
}
/** Used to remove specific component from their respective group. */
remove(serviceName, type) {
const self = this;
if (!serviceName || typeof serviceName !== 'string') {
throw new SoloError(`Service name is required ${serviceName}`);
}
if (!Object.values(ComponentType).includes(type)) {
throw new SoloError(`Invalid component type ${type}`);
}
function deleteComponentCallback(components) {
if (!components[serviceName]) {
throw new SoloError(`Component ${serviceName} of type ${type} not found while attempting to remove`);
}
delete components[serviceName];
}
self.applyCallbackToComponentGroup(type, serviceName, deleteComponentCallback);
}
/* -------- Utilities -------- */
getComponent(type, serviceName) {
let component;
function getComponentCallback(components) {
if (!components[serviceName]) {
throw new SoloError(`Component ${serviceName} of type ${type} not found while attempting to read`);
}
component = components[serviceName];
}
this.applyCallbackToComponentGroup(type, serviceName, getComponentCallback);
return component;
}
/**
* Method used to map the type to the specific component group
* and pass it to a callback to apply modifications
*/
applyCallbackToComponentGroup(type, serviceName, callback) {
switch (type) {
case ComponentType.Relay:
callback(this.relays);
break;
case ComponentType.HaProxy:
callback(this.haProxies);
break;
case ComponentType.MirrorNode:
callback(this.mirrorNodes);
break;
case ComponentType.EnvoyProxy:
callback(this.envoyProxies);
break;
case ComponentType.ConsensusNode:
callback(this.consensusNodes);
break;
case ComponentType.MirrorNodeExplorer:
callback(this.mirrorNodeExplorers);
break;
default:
throw new SoloError(`Unknown component type ${type}, service name: ${serviceName}`);
}
this.validate();
}
/**
* Handles creating instance of the class from plain object.
*
* @param components - component groups distinguished by their type.
*/
static fromObject(components) {
const relays = {};
const haProxies = {};
const mirrorNodes = {};
const envoyProxies = {};
const consensusNodes = {};
const mirrorNodeExplorers = {};
Object.entries(components).forEach(([type, components]) => {
switch (type) {
case ComponentType.Relay:
Object.entries(components).forEach(([name, component]) => {
relays[name] = RelayComponent.fromObject(component);
});
break;
case ComponentType.HaProxy:
Object.entries(components).forEach(([name, component]) => {
haProxies[name] = HaProxyComponent.fromObject(component);
});
break;
case ComponentType.MirrorNode:
Object.entries(components).forEach(([name, component]) => {
mirrorNodes[name] = MirrorNodeComponent.fromObject(component);
});
break;
case ComponentType.EnvoyProxy:
Object.entries(components).forEach(([name, component]) => {
envoyProxies[name] = EnvoyProxyComponent.fromObject(component);
});
break;
case ComponentType.ConsensusNode:
Object.entries(components).forEach(([name, component]) => {
consensusNodes[name] = ConsensusNodeComponent.fromObject(component);
});
break;
case ComponentType.MirrorNodeExplorer:
Object.entries(components).forEach(([name, component]) => {
mirrorNodeExplorers[name] = MirrorNodeExplorerComponent.fromObject(component);
});
break;
default:
throw new SoloError(`Unknown component type ${type}`);
}
});
return new ComponentsDataWrapper(relays, haProxies, mirrorNodes, envoyProxies, consensusNodes, mirrorNodeExplorers);
}
/** Used to create an empty instance used to keep the constructor private */
static initializeEmpty() {
return new ComponentsDataWrapper();
}
static initializeWithNodes(nodeAliases, cluster, namespace) {
const consensusNodeComponents = {};
nodeAliases.forEach((alias, index) => {
consensusNodeComponents[alias] = new ConsensusNodeComponent(alias, cluster, namespace, ConsensusNodeStates.REQUESTED, index);
});
const componentDataWrapper = new ComponentsDataWrapper(undefined, undefined, undefined, undefined, consensusNodeComponents, undefined);
return componentDataWrapper;
}
/** checks if component exists in the respective group */
exists(components, newComponent) {
return Object.values(components).some(component => BaseComponent.compare(component, newComponent));
}
validate() {
function testComponentsObject(components, expectedInstance) {
Object.entries(components).forEach(([name, component]) => {
if (!name || typeof name !== 'string') {
throw new SoloError(`Invalid component service name ${{ [name]: component?.constructor?.name }}`);
}
if (!(component instanceof expectedInstance)) {
throw new SoloError(`Invalid component type, service name: ${name}, ` +
`expected ${expectedInstance?.name}, actual: ${component?.constructor?.name}`, null, { component });
}
});
}
testComponentsObject(this.relays, RelayComponent);
testComponentsObject(this.haProxies, HaProxyComponent);
testComponentsObject(this.mirrorNodes, MirrorNodeComponent);
testComponentsObject(this.envoyProxies, EnvoyProxyComponent);
testComponentsObject(this.consensusNodes, ConsensusNodeComponent);
testComponentsObject(this.mirrorNodeExplorers, MirrorNodeExplorerComponent);
}
toObject() {
function transform(components) {
const transformedComponents = {};
Object.entries(components).forEach(([name, component]) => {
transformedComponents[name] = component.toObject();
});
return transformedComponents;
}
return {
[ComponentType.Relay]: transform(this.relays),
[ComponentType.HaProxy]: transform(this.haProxies),
[ComponentType.MirrorNode]: transform(this.mirrorNodes),
[ComponentType.EnvoyProxy]: transform(this.envoyProxies),
[ComponentType.ConsensusNode]: transform(this.consensusNodes),
[ComponentType.MirrorNodeExplorer]: transform(this.mirrorNodeExplorers),
};
}
clone() {
const data = this.toObject();
return ComponentsDataWrapper.fromObject(data);
}
}
//# sourceMappingURL=components_data_wrapper.js.map