@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
875 lines • 47.3 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var NodeCommandHandlers_1;
import * as helpers from '../../core/helpers.js';
import * as NodeFlags from './flags.js';
import * as constants from '../../core/constants.js';
import { SoloError } from '../../core/errors/solo-error.js';
import { NodeSubcommandType } from '../../core/enumerations.js';
import { NodeHelper } from './helper.js';
import chalk from 'chalk';
import { inject, injectable } from 'tsyringe-neo';
import { patchInject } from '../../core/dependency-injection/container-helper.js';
import { CommandHandler } from '../../core/command-handler.js';
import { InjectTokens } from '../../core/dependency-injection/inject-tokens.js';
import { ComponentTypes } from '../../core/config/remote/enumerations/component-types.js';
import { DeploymentPhase } from '../../data/schema/model/remote/deployment-phase.js';
import { Templates } from '../../core/templates.js';
import { LedgerPhase } from '../../data/schema/model/remote/ledger-phase.js';
import { LocalConfigRuntimeState } from '../../business/runtime-state/config/local/local-config-runtime-state.js';
import { PathEx } from '../../business/utils/path-ex.js';
import { Flags as flags } from '../flags.js';
import { select as selectPrompt } from '@inquirer/prompts';
import { getSoloVersion } from '../../../version.js';
import { DiagnosticsReporter } from '../util/diagnostics-reporter.js';
import { findDeploymentsFromRemoteConfig } from '../util/find-deployments-from-remote-config.js';
import { GetSoloRemoteConfigMapTask } from '../util/get-solo-remote-config-map-task.js';
let NodeCommandHandlers = class NodeCommandHandlers extends CommandHandler {
static { NodeCommandHandlers_1 = this; }
leaseManager;
localConfig;
remoteConfig;
tasks;
configs;
k8Factory;
zippy;
nodeConfigManager;
constructor(leaseManager, configManager, localConfig, remoteConfig, tasks, configs, k8Factory, zippy) {
super();
this.leaseManager = leaseManager;
this.localConfig = localConfig;
this.remoteConfig = remoteConfig;
this.tasks = tasks;
this.configs = configs;
this.k8Factory = k8Factory;
this.zippy = zippy;
this.leaseManager = patchInject(leaseManager, InjectTokens.LockManager, this.constructor.name);
this.nodeConfigManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name);
this.configs = patchInject(configs, InjectTokens.NodeCommandConfigs, this.constructor.name);
this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name);
this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name);
this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name);
this.tasks = patchInject(tasks, InjectTokens.NodeCommandTasks, this.constructor.name);
this.zippy = patchInject(zippy, InjectTokens.Zippy, this.constructor.name);
}
static ADD_CONTEXT_FILE = 'node-add.json';
static DESTROY_CONTEXT_FILE = 'node-destroy.json';
static UPDATE_CONTEXT_FILE = 'node-update.json';
static UPGRADE_CONTEXT_FILE = 'node-upgrade.json';
resolveOutputDirectory(argv, fallback = '') {
this.nodeConfigManager.update(argv);
return this.nodeConfigManager.getFlag(flags.outputDir) || fallback;
}
resolveDeploymentFlag(argv) {
const deploymentFromArgument = argv[flags.deployment.name] || '';
if (deploymentFromArgument) {
return deploymentFromArgument;
}
this.nodeConfigManager.update(argv);
return this.nodeConfigManager.getFlag(flags.deployment) || '';
}
resolveQuietFlag(argv) {
if (argv[flags.quiet.name] !== undefined) {
return argv[flags.quiet.name] === true;
}
this.nodeConfigManager.update(argv);
return this.nodeConfigManager.getFlag(flags.quiet) === true;
}
ensureInteractiveSelectionPrompt() {
if (!process.stdout.isTTY || !process.stdin.isTTY) {
throw new SoloError('Cannot prompt for input in non-interactive mode');
}
}
/** ******** Task Lists **********/
destroyPrepareTaskList(argv, lease) {
return [
this.tasks.initialize(argv, this.configs.destroyConfigBuilder.bind(this.configs), lease),
this.validateSingleNodeState({ excludedPhases: [] }),
this.tasks.identifyExistingNodes(),
this.tasks.loadAdminKey(),
this.tasks.prepareUpgradeZip(),
this.tasks.checkExistingNodesStakedAmount(),
];
}
destroySubmitTransactionsTaskList() {
return [
this.tasks.sendNodeDeleteTransaction(),
this.tasks.sendPrepareUpgradeTransaction(),
this.tasks.sendFreezeUpgradeTransaction(),
];
}
destroyExecuteTaskList() {
return [
this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
this.tasks.stopNodes('existingNodeAliases'),
this.tasks.downloadNodeGeneratedFilesForDynamicAddressBook(),
this.tasks.prepareStagingDirectory('existingNodeAliases'),
this.tasks.refreshNodeList(),
this.tasks.copyNodeKeysToSecrets('refreshedConsensusNodes'),
this.tasks.getNodeLogsAndConfigs(),
this.tasks.updateChartWithConfigMap('Delete network node from chart and update configMaps', NodeSubcommandType.DESTROY),
this.tasks.killNodes(NodeSubcommandType.DESTROY),
this.tasks.sleep('Give time for pods to come up after being killed', 20_000),
this.tasks.checkNodePodsAreRunning(),
this.tasks.populateServiceMap(),
this.tasks.fetchPlatformSoftware('allNodeAliases'),
this.tasks.setupNetworkNodes('allNodeAliases', false),
this.tasks.startNodes('allNodeAliases'),
this.tasks.enablePortForwarding(),
this.tasks.checkAllNodesAreActive('allNodeAliases'),
this.tasks.checkAllNodeProxiesAreActive(),
this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.DESTROY),
this.tasks.finalize(),
];
}
addPrepareTasks(argv, lease) {
return [
this.tasks.initialize(argv, this.configs.addConfigBuilder.bind(this.configs), lease),
// TODO instead of validating the state we need to do a remote config add component, and we will need to manually
// the nodeAlias based on the next available node ID + 1
// this.validateSingleNodeState({excludedPhases: []}),
this.tasks.checkPVCsEnabled(),
this.tasks.identifyExistingNodes(),
this.tasks.determineNewNodeAccountNumber(),
this.tasks.copyGrpcTlsCertificates(),
this.tasks.generateGossipKey(),
this.tasks.generateGrpcTlsKey(),
this.tasks.loadSigningKeyCertificate(),
this.tasks.computeMTLSCertificateHash(),
this.tasks.prepareGossipEndpoints(),
this.tasks.prepareGrpcServiceEndpoints(),
this.tasks.prepareUpgradeZip(),
this.tasks.checkExistingNodesStakedAmount(),
];
}
addSubmitTransactionsTasks() {
return [
this.tasks.sendNodeCreateTransaction(),
this.tasks.sendPrepareUpgradeTransaction(),
this.tasks.sendFreezeUpgradeTransaction(),
];
}
addExecuteTasks() {
return [
this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
this.tasks.downloadNodeGeneratedFilesForDynamicAddressBook(),
this.tasks.prepareStagingDirectory('allNodeAliases'),
this.tasks.addNewConsensusNodeToRemoteConfig(),
this.tasks.copyNodeKeysToSecrets(),
this.tasks.getNodeLogsAndConfigs(),
this.tasks.updateChartWithConfigMap('Deploy new network node', NodeSubcommandType.ADD),
this.tasks.stopNodes('existingNodeAliases'),
this.tasks.killNodes(),
this.tasks.checkNodePodsAreRunning(),
this.tasks.populateServiceMap(),
this.tasks.fetchPlatformSoftware('allNodeAliases'),
this.tasks.downloadLastState(),
this.tasks.uploadStateToNewNode(),
this.tasks.setupNetworkNodes('allNodeAliases', false),
this.tasks.updateBlockNodesJson(),
this.tasks.addWrapsLib(),
this.tasks.startNodes('allNodeAliases'),
this.tasks.enablePortForwarding(),
this.tasks.checkAllNodesAreActive('allNodeAliases'),
this.tasks.checkAllNodeProxiesAreActive(),
this.tasks.waitForTss(),
this.tasks.stakeNewNode(),
this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.ADD),
this.tasks.loadAdminKey(),
this.tasks.setGrpcWebEndpoint('newNodeAliases', NodeSubcommandType.ADD),
this.tasks.finalize(),
];
}
updatePrepareTasks(argv, lease) {
return [
this.tasks.initialize(argv, this.configs.updateConfigBuilder.bind(this.configs), lease),
this.validateSingleNodeState({ excludedPhases: [] }),
this.tasks.identifyExistingNodes(),
this.tasks.loadAdminKey(),
this.tasks.prepareUpgradeZip(),
this.tasks.checkExistingNodesStakedAmount(),
];
}
updateSubmitTransactionsTasks() {
return [
this.tasks.sendNodeUpdateTransaction(),
this.tasks.sendPrepareUpgradeTransaction(),
this.tasks.sendFreezeUpgradeTransaction(),
];
}
updateExecuteTasks() {
return [
this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
this.tasks.downloadNodeGeneratedFilesForDynamicAddressBook(),
this.tasks.prepareStagingDirectory('allNodeAliases'),
this.tasks.copyNodeKeysToSecrets(),
this.tasks.getNodeLogsAndConfigs(),
this.tasks.updateChartWithConfigMap('Update chart to use new configMap due to account number change', NodeSubcommandType.UPDATE, ({ config }) => !config.newAccountNumber && !config.debugNodeAlias),
this.tasks.killNodesAndUpdateConfigMap(),
this.tasks.checkNodePodsAreRunning(),
this.tasks.fetchPlatformSoftware('allNodeAliases'),
this.tasks.setupNetworkNodes('allNodeAliases', false),
this.tasks.addWrapsLib(),
this.tasks.startNodes('allNodeAliases'),
this.tasks.enablePortForwarding(),
this.tasks.checkAllNodesAreActive('allNodeAliases'),
this.tasks.checkAllNodeProxiesAreActive(),
this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.UPDATE),
this.tasks.finalize(),
];
}
upgradePrepareTasks(argv, lease) {
return [
this.tasks.initialize(argv, this.configs.upgradeConfigBuilder.bind(this.configs), lease),
this.validateAllNodePhases({ excludedPhases: [] }),
this.tasks.identifyExistingNodes(),
this.tasks.loadAdminKey(),
this.tasks.prepareUpgradeZip(),
this.tasks.checkExistingNodesStakedAmount(),
];
}
upgradeSubmitTransactionsTasks() {
return [
this.tasks.sendPrepareUpgradeTransaction(),
this.tasks.sendFreezeUpgradeTransaction(),
];
}
upgradeExecuteTasks() {
return [
this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
this.tasks.downloadNodeUpgradeFiles(),
this.tasks.getNodeLogsAndConfigs(),
this.tasks.upgradeNodeConfigurationFilesWithChart(),
this.tasks.fetchPlatformSoftware('nodeAliases'),
this.tasks.addWrapsLib(),
this.tasks.startNodes('allNodeAliases'),
this.tasks.enablePortForwarding(),
this.tasks.checkAllNodesAreActive('allNodeAliases'),
this.tasks.checkAllNodeProxiesAreActive(),
this.tasks.finalize(),
];
}
/** ******** Handlers **********/
async prepareUpgrade(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.PREPARE_UPGRADE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.prepareUpgradeConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.tasks.identifyExistingNodes(),
this.tasks.prepareStagingDirectory('existingNodeAliases'),
this.tasks.prepareUpgradeZip(),
this.tasks.sendPrepareUpgradeTransaction(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in preparing node upgrade', leaseWrapper.lease);
return true;
}
async freezeUpgrade(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.PREPARE_UPGRADE_FLAGS);
await this.commandAction(argv, [
this.tasks.initialize(argv, this.configs.prepareUpgradeConfigBuilder.bind(this.configs)),
this.tasks.identifyExistingNodes(),
this.tasks.prepareUpgradeZip(),
this.tasks.sendFreezeUpgradeTransaction(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in executing node freeze upgrade');
return true;
}
async update(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.updatePrepareTasks(argv, leaseWrapper.lease),
...this.updateSubmitTransactionsTasks(),
...this.updateExecuteTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in updating consensus nodes', leaseWrapper.lease);
return true;
}
async updatePrepare(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_PREPARE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.updatePrepareTasks(argv, leaseWrapper.lease),
this.tasks.saveContextData(argv, NodeCommandHandlers_1.UPDATE_CONTEXT_FILE, NodeHelper.updateSaveContextParser),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in preparing consensus node update', leaseWrapper.lease);
return true;
}
async updateSubmitTransactions(argv) {
const leaseWrapper = { lease: undefined };
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_SUBMIT_TRANSACTIONS_FLAGS);
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.updateConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser),
...this.updateSubmitTransactionsTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in submitting transactions for consensus node update', leaseWrapper.lease);
return true;
}
async updateExecute(argv) {
const leaseWrapper = { lease: undefined };
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_EXECUTE_FLAGS);
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.updateConfigBuilder.bind(this.configs), leaseWrapper.lease, false),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser),
...this.updateExecuteTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in executing network upgrade', leaseWrapper.lease);
return true;
}
async upgradePrepare(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_PREPARE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.upgradePrepareTasks(argv, leaseWrapper.lease),
this.tasks.saveContextData(argv, NodeCommandHandlers_1.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeSaveContextParser),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in preparing node upgrade', leaseWrapper.lease);
return true;
}
async upgradeSubmitTransactions(argv) {
const leaseWrapper = { lease: undefined };
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_SUBMIT_TRANSACTIONS_FLAGS);
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.upgradeConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser),
...this.upgradeSubmitTransactionsTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in submitting transactions for node upgrade', leaseWrapper.lease);
return true;
}
async upgradeExecute(argv) {
const leaseWrapper = { lease: undefined };
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS);
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.upgradeConfigBuilder.bind(this.configs), leaseWrapper.lease, false),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser),
...this.upgradeExecuteTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in executing network upgrade', leaseWrapper.lease);
return true;
}
async upgrade(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.upgradePrepareTasks(argv, leaseWrapper.lease),
...this.upgradeSubmitTransactionsTasks(),
...this.upgradeExecuteTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in upgrade network', leaseWrapper.lease);
return true;
}
async destroy(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.destroyPrepareTaskList(argv, leaseWrapper.lease),
...this.destroySubmitTransactionsTaskList(),
...this.destroyExecuteTaskList(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in destroying nodes', leaseWrapper.lease);
return true;
}
async destroyPrepare(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_PREPARE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.destroyPrepareTaskList(argv, leaseWrapper.lease),
this.tasks.saveContextData(argv, NodeCommandHandlers_1.DESTROY_CONTEXT_FILE, NodeHelper.deleteSaveContextParser),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in preparing to destroy a node', leaseWrapper.lease);
return true;
}
async destroySubmitTransactions(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_SUBMIT_TRANSACTIONS_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.destroyConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.DESTROY_CONTEXT_FILE, NodeHelper.deleteLoadContextParser),
...this.destroySubmitTransactionsTaskList(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in deleting a node', leaseWrapper.lease);
return true;
}
async destroyExecute(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_EXECUTE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.destroyConfigBuilder.bind(this.configs), leaseWrapper.lease, false),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.DESTROY_CONTEXT_FILE, NodeHelper.deleteLoadContextParser),
...this.destroyExecuteTaskList(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in deleting a node', leaseWrapper.lease);
return true;
}
async add(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.addPrepareTasks(argv, leaseWrapper.lease),
...this.addSubmitTransactionsTasks(),
...this.addExecuteTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in adding consensus node', leaseWrapper.lease);
return true;
}
async addPrepare(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_PREPARE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
...this.addPrepareTasks(argv, leaseWrapper.lease),
this.tasks.saveContextData(argv, NodeCommandHandlers_1.ADD_CONTEXT_FILE, helpers.addSaveContextParser),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in preparing node', leaseWrapper.lease);
return true;
}
async addSubmitTransactions(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_SUBMIT_TRANSACTIONS_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.addConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.ADD_CONTEXT_FILE, helpers.addLoadContextParser),
...this.addSubmitTransactionsTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, '`Error in submitting transactions to node', leaseWrapper.lease);
return true;
}
async addExecute(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_EXECUTE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.addConfigBuilder.bind(this.configs), leaseWrapper.lease, false),
this.tasks.identifyExistingNodes(),
this.tasks.loadContextData(argv, NodeCommandHandlers_1.ADD_CONTEXT_FILE, helpers.addLoadContextParser),
...this.addExecuteTasks(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in adding node', leaseWrapper.lease);
return true;
}
async logs(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.LOGS_FLAGS);
if (!argv[flags.deployment.name]) {
argv[flags.deployment.name] = await this.resolveDeploymentForLogs(argv);
}
const outputDirectory = this.resolveOutputDirectory(argv);
await this.commandAction(argv, [
this.tasks.initialize(argv, this.configs.logsConfigBuilder.bind(this.configs), null, true, false),
this.tasks.getNodeLogsAndConfigs(undefined, outputDirectory),
this.tasks.getHelmChartValues(outputDirectory),
GetSoloRemoteConfigMapTask.getTask(this.k8Factory, this.logger, outputDirectory),
this.tasks.downloadHieroComponentLogs(outputDirectory),
this.tasks.analyzeCollectedDiagnostics(outputDirectory),
this.tasks.reportActivePortForwards(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in downloading logs from nodes');
this.logger.showUser(chalk.yellow('\n⚠ Warning: Collected diagnostic data contains sensitive node configuration\n' +
' (TLS certificates, private keys, onboard data). Store it securely and do\n' +
' not share publicly without reviewing the contents first.'));
return true;
}
async analyze(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.ANALYZE_FLAGS);
this.nodeConfigManager.update(argv);
const inputDirectory = this.nodeConfigManager.getFlag(flags.inputDir) || '';
await this.commandAction(argv, [this.tasks.analyzeCollectedDiagnostics(inputDirectory)], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error analyzing diagnostics logs');
return true;
}
async resolveDeploymentForLogs(argv) {
const deploymentFromFlag = this.resolveDeploymentFlag(argv);
if (deploymentFromFlag && deploymentFromFlag.trim()) {
return deploymentFromFlag;
}
await this.localConfig.load();
const deployments = this.localConfig.configuration.deployments;
const validDeployments = [];
for (const deployment of deployments) {
if (deployment?.name && deployment.name.trim().length > 0) {
validDeployments.push(deployment);
}
}
if (validDeployments.length === 0) {
const remoteDeployments = await findDeploymentsFromRemoteConfig(this.k8Factory, this.logger);
if (remoteDeployments.size === 0) {
throw new SoloError(`No deployments found in local or remote config. Please provide --${flags.deployment.name} or create a deployment first.`);
}
const remoteDeploymentNames = [...remoteDeployments.keys()];
if (remoteDeploymentNames.length === 1) {
const selectedFromRemote = remoteDeploymentNames[0];
this.logger.showUser(`Using deployment from remote config: ${selectedFromRemote}`);
return selectedFromRemote;
}
if (this.resolveQuietFlag(argv)) {
const names = remoteDeploymentNames.join(', ');
throw new SoloError(`Multiple deployments found in remote config (${names}). Please provide --${flags.deployment.name}.`);
}
this.ensureInteractiveSelectionPrompt();
const selectedFromRemote = (await selectPrompt({
message: 'Select deployment for diagnostics logs:',
choices: remoteDeploymentNames.map((name) => ({ name, value: name })),
}));
this.logger.showUser(`Using selected deployment: ${selectedFromRemote}`);
return selectedFromRemote;
}
if (validDeployments.length === 1) {
const deploymentName = validDeployments[0].name;
this.logger.showUser(`Using deployment from local config: ${deploymentName}`);
return deploymentName;
}
if (this.resolveQuietFlag(argv)) {
const deploymentNames = validDeployments.map((deployment) => deployment.name).join(', ');
throw new SoloError(`Multiple deployments found in local config (${deploymentNames}). Please provide --${flags.deployment.name}.`);
}
this.ensureInteractiveSelectionPrompt();
const selectedDeployment = (await selectPrompt({
message: 'Select deployment for diagnostics logs:',
choices: validDeployments.map((deployment) => ({
name: deployment.name,
value: deployment.name,
})),
}));
this.logger.showUser(`Using selected deployment: ${selectedDeployment}`);
return selectedDeployment;
}
async all(argv, excludeSensitiveData = false) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.DIAGNOSTICS_CONNECTIONS);
if (!argv[flags.deployment.name]) {
argv[flags.deployment.name] = await this.resolveDeploymentForLogs(argv);
}
const outputDirectory = this.resolveOutputDirectory(argv);
await this.commandAction(argv, [
this.tasks.initialize(argv, this.configs.logsConfigBuilder.bind(this.configs), null, true, false),
this.tasks.getNodeLogsAndConfigs(excludeSensitiveData, outputDirectory),
...(excludeSensitiveData ? [] : [this.tasks.getHelmChartValues(outputDirectory)]),
GetSoloRemoteConfigMapTask.getTask(this.k8Factory, this.logger, outputDirectory),
this.tasks.downloadHieroComponentLogs(outputDirectory),
this.tasks.analyzeCollectedDiagnostics(outputDirectory),
// do not call validateConnectionsTaskList since node could be stopped or not active but logs are still needed
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in diagnosing deployment');
return true;
}
async debug(argv, excludeSensitiveData = false) {
// First run all diagnostics
await this.all(argv, excludeSensitiveData);
// Then create a zip file from the logs directory
const outputDirectory = this.resolveOutputDirectory(argv, constants.SOLO_LOGS_DIR);
const deployment = this.resolveDeploymentFlag(argv);
const timestamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-').slice(0, 19);
const zipFileName = `solo-debug-${deployment}-${timestamp}.zip`;
const zipFilePath = PathEx.join(outputDirectory, '..', zipFileName);
this.logger.showUser(chalk.cyan(`\nCreating debug archive from: ${outputDirectory}`));
this.logger.showUser(chalk.cyan(`Archive location: ${zipFilePath}`));
if (!excludeSensitiveData) {
this.logger.showUser(chalk.yellow('\n⚠ Warning: The debug archive contains sensitive node configuration\n' +
' (TLS certificates, private keys, onboard data). Review its contents\n' +
' before sharing. Private keys under data/keys are NOT excluded.'));
}
try {
await this.zippy.zip(outputDirectory, zipFilePath);
this.logger.showUser(chalk.green('✓ Debug information collected successfully!'));
this.logger.showUser(chalk.cyan(` Archive: ${zipFilePath}`));
}
catch (error) {
throw new SoloError(`Failed to create debug archive: ${error.message}`, error);
}
return true;
}
async connections(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.DIAGNOSTICS_CONNECTIONS);
await this.commandAction(argv, [
this.tasks.initialize(argv, this.configs.connectionsConfigBuilder.bind(this.configs)),
...this.validateConnectionsTaskList(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in testing connections to components');
return true;
}
validateConnectionsTaskList() {
return [
this.tasks.prepareDiagnosticsData(),
this.tasks.validateLocalPorts(),
this.tasks.testAccountCreation(),
this.tasks.fetchAccountFromExplorer(),
this.tasks.testRelay(),
];
}
/**
* Collects a full debug archive for the deployment (logs + configs + zip) and
* then creates a GitHub issue using the `gh` CLI with a pre-filled title and body.
* The generated archive is referenced for the user to attach manually via the GitHub UI.
*
*/
async report(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.REPORT_FLAGS);
// Resolve deployment before calling collectDebug() so it's available for the issue title/body
const deployment = argv[flags.deployment.name]
? String(argv[flags.deployment.name])
: await this.resolveDeploymentForLogs(argv);
if (!argv[flags.deployment.name]) {
argv[flags.deployment.name] = deployment;
}
await DiagnosticsReporter.runDiagnosticsReport({
logger: this.logger,
deployment,
outputDirectory: this.resolveOutputDirectory(argv, constants.SOLO_LOGS_DIR),
soloVersion: getSoloVersion(),
isQuiet: this.resolveQuietFlag(argv),
collectDebug: async () => {
await this.debug(argv, true);
},
});
return true;
}
async states(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.STATES_FLAGS);
await this.commandAction(argv, [
this.tasks.initialize(argv, this.configs.statesConfigBuilder.bind(this.configs)),
this.tasks.getNodeStateFiles(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in downloading states from nodes');
return true;
}
async refresh(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.REFRESH_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.refreshConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.validateAllNodePhases({
acceptedPhases: [DeploymentPhase.STARTED, DeploymentPhase.CONFIGURED, DeploymentPhase.DEPLOYED],
}),
this.tasks.identifyNetworkPods(),
this.tasks.dumpNetworkNodesSaveState(),
this.tasks.downloadLastState(),
this.tasks.uploadStateToNewNode(),
this.tasks.fetchPlatformSoftware('nodeAliases'),
this.tasks.setupNetworkNodes('nodeAliases', true),
this.tasks.startNodes('nodeAliases'),
this.tasks.checkNodesAndProxiesAreActive('nodeAliases'),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in refreshing nodes', leaseWrapper.lease);
return true;
}
async keys(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.KEYS_FLAGS);
await this.commandAction(argv, [
this.tasks.initialize(argv, this.configs.keysConfigBuilder.bind(this.configs)),
this.tasks.generateGossipKeys(),
this.tasks.generateGrpcTlsKeys(),
this.tasks.finalize(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error generating keys', undefined, 'keys consensus generate');
return true;
}
async stop(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.STOP_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.stopConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.validateAllNodePhases({
acceptedPhases: [DeploymentPhase.STARTED, DeploymentPhase.CONFIGURED],
}),
this.tasks.identifyNetworkPods(1),
this.tasks.stopNodes('nodeAliases'),
this.changeAllNodePhases(DeploymentPhase.STOPPED),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error stopping node', leaseWrapper.lease);
return true;
}
async start(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.START_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.startConfigBuilder.bind(this.configs), leaseWrapper.lease, true, false),
this.validateAllNodePhases({ acceptedPhases: [DeploymentPhase.CONFIGURED] }),
this.tasks.identifyExistingNodes(),
this.tasks.uploadStateFiles(({ config }) => config.stateFile.length === 0),
this.tasks.startNodes('nodeAliases'),
this.tasks.enablePortForwarding(true),
this.tasks.checkNodesAndProxiesAreActive('nodeAliases'),
this.tasks.waitForTss(),
this.tasks.setGrpcWebEndpoint('nodeAliases', NodeSubcommandType.START),
this.changeAllNodePhases(DeploymentPhase.STARTED, LedgerPhase.INITIALIZED),
this.tasks.addNodeStakes(),
this.tasks.emitNodeStartedEvent(),
// TODO only show this if we are not running in one-shot mode
// this.tasks.showUserMessages(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error starting node', leaseWrapper.lease, 'consensus node start');
return true;
}
async setup(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.SETUP_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.setupConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.validateAllNodePhases({
acceptedPhases: [DeploymentPhase.DEPLOYED],
}),
this.tasks.identifyNetworkPods(),
this.tasks.fetchPlatformSoftware('nodeAliases'),
this.tasks.setupNetworkNodes('nodeAliases', true),
this.tasks.setupNetworkNodeFolders(),
this.changeAllNodePhases(DeploymentPhase.CONFIGURED),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error in setting up nodes', leaseWrapper.lease, 'consensus node setup');
return true;
}
async freeze(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.FREEZE_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager, false),
this.tasks.initialize(argv, this.configs.freezeConfigBuilder.bind(this.configs), leaseWrapper.lease, true, false),
this.tasks.identifyExistingNodes(),
this.tasks.sendFreezeTransaction(),
this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
this.tasks.stopNodes('existingNodeAliases'),
this.changeAllNodePhases(DeploymentPhase.FROZEN),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error freezing node', leaseWrapper.lease);
return true;
}
async restart(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.RESTART_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.restartConfigBuilder.bind(this.configs), leaseWrapper.lease, true, false),
this.tasks.identifyExistingNodes(),
this.tasks.addWrapsLib(),
this.tasks.startNodes('existingNodeAliases'),
this.tasks.enablePortForwarding(),
this.tasks.checkNodesAndProxiesAreActive('existingNodeAliases'),
this.changeAllNodePhases(DeploymentPhase.STARTED),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error restarting node', leaseWrapper.lease);
return true;
}
/**
* Changes the state from all consensus nodes components in remote config.
*
* @param phase - to which to change the consensus node component
* @param ledgerPhase
*/
changeAllNodePhases(phase, ledgerPhase = undefined) {
return {
title: `Change node state to ${phase} in remote config`,
skip: () => !this.remoteConfig.isLoaded(),
task: async (context_) => {
for (const consensusNode of context_.config.consensusNodes) {
const componentId = Templates.renderComponentIdFromNodeAlias(consensusNode.name);
this.remoteConfig.configuration.components.changeNodePhase(componentId, phase);
}
if (ledgerPhase) {
this.remoteConfig.configuration.state.ledgerPhase = ledgerPhase;
}
await this.remoteConfig.persist();
},
};
}
/**
* Creates tasks to validate that each node state is either one of the accepted states or not one of the excluded.
*
* @param acceptedPhases - the state at which the nodes can be, not matching any of the states throws an error
* @param excludedPhases - the state at which the nodes can't be, matching any of the states throws an error
*/
validateAllNodePhases({ acceptedPhases, excludedPhases, }) {
return {
title: 'Validate nodes states',
skip: () => !this.remoteConfig.isLoaded(),
task: (context_, task) => {
const nodeAliases = context_.config.nodeAliases;
const subTasks = nodeAliases.map((nodeAlias) => ({
title: `Validating state for node ${nodeAlias}`,
task: (_, task) => {
const state = this.validateNodeState(nodeAlias, this.remoteConfig.configuration.components, acceptedPhases, excludedPhases);
task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`;
},
}));
return task.newListr(subTasks, {
concurrent: false,
rendererOptions: { collapseSubtasks: false },
});
},
};
}
/**
* Creates tasks to validate that specific node state is either one of the accepted states or not one of the excluded.
*
* @param acceptedPhases - the state at which the node can be, not matching any of the states throws an error
* @param excludedPhases - the state at which the node can't be, matching any of the states throws an error
*/
validateSingleNodeState({ acceptedPhases, excludedPhases, }) {
void acceptedPhases;
void excludedPhases;
return {
title: 'Validate nodes state',
skip: () => !this.remoteConfig.isLoaded(),
task: (context_, task) => {
const nodeAlias = context_.config.nodeAlias;
task.title += ` ${nodeAlias}`;
// TODO: Disabled for now until the node's state mapping is completed
// const components = this.remoteConfig.components;
// const state = this.validateNodeState(nodeAlias, components, acceptedPhases, excludedPhases);
// task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`;
},
};
}
/**
* @param nodeAlias - the alias of the node whose state to validate
* @param components - the component data wrapper
* @param acceptedPhases - the state at which the node can be, not matching any of the states throws an error
* @param excludedPhases - the state at which the node can't be, matching any of the states throws an error
*/
validateNodeState(nodeAlias, components, acceptedPhases, excludedPhases) {
void acceptedPhases;
void excludedPhases;
let nodeComponent;
try {
nodeComponent = components.getComponent(ComponentTypes.ConsensusNode, Templates.renderComponentIdFromNodeAlias(nodeAlias));
}
catch {
throw new SoloError(`${nodeAlias} not found in remote config`);
}
// TODO: Enable once states have been mapped
// if (acceptedPhases && !acceptedPhases.includes(nodeComponent.state)) {
// const errorMessageData =
// `accepted states: ${acceptedPhases.join(', ')}, ` + `current state: ${nodeComponent.state}`;
// throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData);
// }
//
// if (excludedPhases && excludedPhases.includes(nodeComponent.state)) {
// const errorMessageData =
// `excluded states: ${excludedPhases.join(', ')}, ` + `current state: ${nodeComponent.state}`;
// throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData);
// }
return nodeComponent.metadata.phase;
}
async collectJavaFlightRecorderLogs(argv) {
argv = helpers.addFlagsToArgv(argv, NodeFlags.COLLECT_JFR_FLAGS);
const leaseWrapper = { lease: undefined };
await this.commandAction(argv, [
this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
this.tasks.initialize(argv, this.configs.collectJfrConfigBuilder.bind(this.configs), leaseWrapper.lease),
this.tasks.downloadJavaFlightRecorderLogs(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, 'Error collecting jfr recording from node', leaseWrapper.lease);
return true;
}
};
NodeCommandHandlers = NodeCommandHandlers_1 = __decorate([
injectable(),
__param(0, inject(InjectTokens.LockManager)),
__param(1, inject(InjectTokens.ConfigManager)),
__param(2, inject(InjectTokens.LocalConfigRuntimeState)),
__param(3, inject(InjectTokens.RemoteConfigRuntimeState)),
__param(4, inject(InjectTokens.NodeCommandTasks)),
__param(5, inject(InjectTokens.NodeCommandConfigs)),
__param(6, inject(InjectTokens.K8Factory)),
__param(7, inject(InjectTokens.Zippy)),
__metadata("design:paramtypes", [Function, Function, LocalConfigRuntimeState, Object, Function, Function, Object, Function])
], NodeCommandHandlers);
export { NodeCommandHandlers };
//# sourceMappingURL=handlers.js.map