diff --git a/fullstack-network-manager/src/commands/base.mjs b/fullstack-network-manager/src/commands/base.mjs index 90f33dcbd..edfeed706 100644 --- a/fullstack-network-manager/src/commands/base.mjs +++ b/fullstack-network-manager/src/commands/base.mjs @@ -1,15 +1,12 @@ 'use strict' import { MissingArgumentError } from '../core/errors.mjs' import { ShellRunner } from '../core/shell_runner.mjs' -import * as flags from './flags.mjs' export class BaseCommand extends ShellRunner { - async prepareChartPath (config, chartRepo, chartName) { - if (!config) throw new MissingArgumentError('config is required') + async prepareChartPath (chartDir, chartRepo, chartName) { if (!chartRepo) throw new MissingArgumentError('chart repo name is required') if (!chartName) throw new MissingArgumentError('chart name is required') - const chartDir = this.configManager.flagValue(config, flags.chartDirectory) if (chartDir) { const chartPath = `${chartDir}/${chartName}` await this.helm.dependency('update', chartPath) diff --git a/fullstack-network-manager/src/commands/chart.mjs b/fullstack-network-manager/src/commands/chart.mjs index 9d81b664e..0bb676ccb 100644 --- a/fullstack-network-manager/src/commands/chart.mjs +++ b/fullstack-network-manager/src/commands/chart.mjs @@ -1,9 +1,10 @@ -import chalk from 'chalk' +import { Listr } from 'listr2' import { FullstackTestingError } from '../core/errors.mjs' import { BaseCommand } from './base.mjs' import * as flags from './flags.mjs' import * as paths from 'path' import { constants } from '../core/index.mjs' +import * as prompts from './prompts.mjs' export class ChartCommand extends BaseCommand { prepareValuesFiles (valuesFile) { @@ -19,13 +20,8 @@ export class ChartCommand extends BaseCommand { return valuesArg } - prepareValuesArg (config) { - const valuesFile = this.configManager.flagValue(config, flags.valuesFile) - const deployMirrorNode = this.configManager.flagValue(config, flags.deployMirrorNode) - const deployHederaExplorer = this.configManager.flagValue(config, flags.deployHederaExplorer) - + prepareValuesArg (chartDir, valuesFile, deployMirrorNode, deployHederaExplorer) { let valuesArg = '' - const chartDir = this.configManager.flagValue(config, flags.chartDirectory) if (chartDir) { valuesArg = `-f ${chartDir}/fullstack-deployment/values.yaml` } @@ -37,56 +33,149 @@ export class ChartCommand extends BaseCommand { return valuesArg } - async installFSTChart (config) { - try { - const namespace = this.configManager.flagValue(config, flags.namespace) - const valuesArg = this.prepareValuesArg(config) - const chartPath = await this.prepareChartPath(config, constants.CHART_FST_REPO_NAME, constants.CHART_FST_DEPLOYMENT_NAME) - - await this.chartManager.install(namespace, constants.CHART_FST_DEPLOYMENT_NAME, chartPath, config.version, valuesArg) - - this.logger.showUser(chalk.cyan('> waiting for network-node pods to be active (first deployment takes ~10m) ...')) - await this.kubectl.wait('pod', - '--for=jsonpath=\'{.status.phase}\'=Running', - '-l fullstack.hedera.com/type=network-node', - '--timeout=900s' - ) - this.logger.showUser(chalk.green('OK'), 'network-node pods are running') - } catch (e) { - throw new FullstackTestingError(`failed install '${constants.CHART_FST_DEPLOYMENT_NAME}' chart`, e) + async prepareConfig (task, argv) { + const cachedConfig = await this.configManager.setupConfig(argv) + const namespace = this.configManager.flagValue(cachedConfig, flags.namespace) + const chartDir = this.configManager.flagValue(cachedConfig, flags.chartDirectory) + const valuesFile = this.configManager.flagValue(cachedConfig, flags.valuesFile) + const deployMirrorNode = this.configManager.flagValue(cachedConfig, flags.deployMirrorNode) + const deployExplorer = this.configManager.flagValue(cachedConfig, flags.deployHederaExplorer) + + // prompt if values are missing and create a config object + const config = { + namespace: await prompts.promptNamespaceArg(task, namespace), + chartDir: await prompts.promptChartDir(task, chartDir), + valuesFile: await prompts.promptChartDir(task, valuesFile), + deployMirrorNode: await prompts.promptDeployMirrorNode(task, deployMirrorNode), + deployHederaExplorer: await prompts.promptDeployHederaExplorer(task, deployExplorer), + timeout: '900s', + version: cachedConfig.version } + + // compute values + config.chartPath = await this.prepareChartPath(config.chartDir, + constants.CHART_FST_REPO_NAME, constants.CHART_FST_DEPLOYMENT_NAME) + + config.valuesArg = this.prepareValuesArg(config.chartDir, + config.valuesFile, config.deployMirrorNode, config.deployHederaExplorer) + + return config } async install (argv) { - try { - const config = await this.configManager.setupConfig(argv) - const namespace = this.configManager.flagValue(config, flags.namespace) + const self = this - await this.installFSTChart(config) + const tasks = new Listr([ + { + title: 'Initialize', + task: async (ctx, task) => { + ctx.config = await self.prepareConfig(task, argv) + } + }, + { + title: `Install chart '${constants.CHART_FST_DEPLOYMENT_NAME}'`, + task: async (ctx, _) => { + await this.chartManager.install( + ctx.config.namespace, + constants.CHART_FST_DEPLOYMENT_NAME, + ctx.config.chartPath, + ctx.config.version, + ctx.config.valuesArg) + } + }, + { + title: 'Waiting for network pods to be ready', + task: async (ctx, _) => { + const timeout = ctx.config.timeout || '900s' + await this.kubectl.wait('pod', + '--for=jsonpath=\'{.status.phase}\'=Running', + '-l fullstack.hedera.com/type=network-node', + `--timeout=${timeout}` + ) + } + } + ]) - this.logger.showList('Deployed Charts', await this.chartManager.getInstalledCharts(namespace)) - return true + try { + await tasks.run() } catch (e) { - this.logger.showUserError(e) + throw new FullstackTestingError(`Error installing chart ${constants.CHART_FST_DEPLOYMENT_NAME}`, e) } - return false + return true } async uninstall (argv) { - const namespace = argv.namespace + const self = this - return await this.chartManager.uninstall(namespace, constants.CHART_FST_DEPLOYMENT_NAME) + const tasks = new Listr([ + { + title: 'Initialize', + task: async (ctx, task) => { + const cachedConfig = await self.configManager.setupConfig(argv) + const namespace = self.configManager.flagValue(cachedConfig, flags.namespace) + ctx.config = { + namespace: await prompts.promptNamespaceArg(task, namespace) + } + } + }, + { + title: `Uninstall chart ${constants.CHART_FST_DEPLOYMENT_NAME}`, + task: async (ctx, _) => { + await self.chartManager.uninstall(ctx.config.namespace, constants.CHART_FST_DEPLOYMENT_NAME) + } + } + ]) + + try { + await tasks.run() + } catch (e) { + throw new FullstackTestingError('Error starting node', e) + } + + return true } async upgrade (argv) { - const namespace = argv.namespace + const self = this - const config = await this.configManager.setupConfig(argv) - const valuesArg = this.prepareValuesArg(argv, config) - const chartPath = await this.prepareChartPath(config) + const tasks = new Listr([ + { + title: 'Initialize', + task: async (ctx, task) => { + ctx.config = await self.prepareConfig(task, argv) + } + }, + { + title: `Upgrade chart '${constants.CHART_FST_DEPLOYMENT_NAME}'`, + task: async (ctx, _) => { + await this.chartManager.upgrade( + ctx.config.namespace, + constants.CHART_FST_DEPLOYMENT_NAME, + ctx.config.chartPath, + ctx.config.valuesArg) + } + }, + { + title: 'Waiting for network pods to be ready', + task: async (ctx, _) => { + const timeout = ctx.config.timeout || '900s' + await this.kubectl.wait('pod', + '--for=jsonpath=\'{.status.phase}\'=Running', + '-l fullstack.hedera.com/type=network-node', + `--timeout=${timeout}` + ) + } + } + ]) + + try { + await tasks.run() + } catch (e) { + throw new FullstackTestingError(`Error upgrading chart ${constants.CHART_FST_DEPLOYMENT_NAME}`, e) + } - return await this.chartManager.upgrade(namespace, constants.CHART_FST_DEPLOYMENT_NAME, chartPath, valuesArg) + return true } static getCommandDefinition (chartCmd) { @@ -116,6 +205,9 @@ export class ChartCommand extends BaseCommand { chartCmd.logger.debug('==== Finished running `chart install`====') if (!r) process.exit(1) + }).catch(err => { + chartCmd.logger.showUserError(err) + process.exit(1) }) } }) @@ -131,6 +223,9 @@ export class ChartCommand extends BaseCommand { chartCmd.logger.debug('==== Finished running `chart uninstall`====') if (!r) process.exit(1) + }).catch(err => { + chartCmd.logger.showUserError(err) + process.exit(1) }) } }) @@ -152,6 +247,9 @@ export class ChartCommand extends BaseCommand { chartCmd.logger.debug('==== Finished running `chart upgrade`====') if (!r) process.exit(1) + }).catch(err => { + chartCmd.logger.showUserError(err) + process.exit(1) }) } }) diff --git a/fullstack-network-manager/src/commands/cluster.mjs b/fullstack-network-manager/src/commands/cluster.mjs index 9c4d84e76..9a7e5067f 100644 --- a/fullstack-network-manager/src/commands/cluster.mjs +++ b/fullstack-network-manager/src/commands/cluster.mjs @@ -1,10 +1,10 @@ -import {Listr} from 'listr2' -import {FullstackTestingError} from '../core/errors.mjs' +import { Listr } from 'listr2' +import { FullstackTestingError } from '../core/errors.mjs' import * as core from '../core/index.mjs' import * as flags from './flags.mjs' -import {BaseCommand} from './base.mjs' +import { BaseCommand } from './base.mjs' import chalk from 'chalk' -import {constants} from '../core/index.mjs' +import { constants } from '../core/index.mjs' import * as prompts from './prompts.mjs' /** @@ -15,7 +15,7 @@ export class ClusterCommand extends BaseCommand { * List available clusters * @returns {Promise} */ - async getClusters() { + async getClusters () { try { return await this.kind.getClusters('-q') } catch (e) { @@ -25,7 +25,7 @@ export class ClusterCommand extends BaseCommand { return [] } - async showClusterList() { + async showClusterList () { this.logger.showList('Clusters', await this.getClusters()) return true } @@ -34,7 +34,7 @@ export class ClusterCommand extends BaseCommand { * List available namespaces * @returns {Promise} */ - async getNameSpaces() { + async getNameSpaces () { try { return await this.kubectl.getNamespace('--no-headers', '-o name') } catch (e) { @@ -49,7 +49,7 @@ export class ClusterCommand extends BaseCommand { * @param argv arguments containing cluster name * @returns {Promise} */ - async getClusterInfo(argv) { + async getClusterInfo (argv) { try { const clusterName = argv.clusterName const cmd = `kubectl cluster-info --context kind-${clusterName}` @@ -66,7 +66,7 @@ export class ClusterCommand extends BaseCommand { return false } - async createNamespace(argv) { + async createNamespace (argv) { try { const namespace = argv.namespace const namespaces = await this.getNameSpaces() @@ -95,7 +95,7 @@ export class ClusterCommand extends BaseCommand { * Show list of installed chart * @param namespace */ - async showInstalledChartList(namespace) { + async showInstalledChartList (namespace) { this.logger.showList('Installed Charts', await this.chartManager.getInstalledCharts(namespace)) } @@ -104,7 +104,7 @@ export class ClusterCommand extends BaseCommand { * @param argv command arguments * @returns {Promise} */ - async create(argv) { + async create (argv) { const self = this const tasks = new Listr([ @@ -177,7 +177,7 @@ export class ClusterCommand extends BaseCommand { * @param argv * @returns {Promise} */ - async delete(argv) { + async delete (argv) { const self = this const tasks = new Listr([ @@ -219,7 +219,7 @@ export class ClusterCommand extends BaseCommand { * @param argv * @returns {Promise} */ - async setup(argv) { + async setup (argv) { const self = this const tasks = new Listr([ @@ -227,7 +227,7 @@ export class ClusterCommand extends BaseCommand { title: 'Initialize', task: async (ctx, task) => { const cachedConfig = await self.configManager.setupConfig(argv) - self.logger.debug('Setup cached config', {cachedConfig, argv}) + self.logger.debug('Setup cached config', { cachedConfig, argv }) // extract config values const clusterName = self.configManager.flagValue(cachedConfig, flags.clusterName) @@ -255,7 +255,7 @@ export class ClusterCommand extends BaseCommand { deployCertManagerCRDs: await prompts.promptDeployCertManagerCRDs(task, deployCertManagerCRDs, namespaces) } - self.logger.debug('Prepare ctx.config', {config: ctx.config, argv}) + self.logger.debug('Prepare ctx.config', { config: ctx.config, argv }) ctx.isChartInstalled = await this.chartManager.isChartInstalled(ctx.config.namespace, constants.CHART_FST_SETUP_NAME) } @@ -305,7 +305,7 @@ export class ClusterCommand extends BaseCommand { * @param argv * @returns {Promise} */ - async reset(argv) { + async reset (argv) { const self = this const tasks = new Listr([ @@ -352,7 +352,7 @@ export class ClusterCommand extends BaseCommand { * Return Yargs command definition for 'cluster' command * @param clusterCmd an instance of ClusterCommand */ - static getCommandDefinition(clusterCmd) { + static getCommandDefinition (clusterCmd) { return { command: 'cluster', desc: 'Manage FST cluster', @@ -363,7 +363,7 @@ export class ClusterCommand extends BaseCommand { desc: 'Create a cluster', builder: y => flags.setCommandFlags(y, flags.clusterName, flags.namespace), handler: argv => { - clusterCmd.logger.debug("==== Running 'cluster create' ===", {argv}) + clusterCmd.logger.debug("==== Running 'cluster create' ===", { argv }) clusterCmd.create(argv).then(r => { clusterCmd.logger.debug('==== Finished running `cluster create`====') @@ -380,7 +380,7 @@ export class ClusterCommand extends BaseCommand { desc: 'Delete a cluster', builder: y => flags.setCommandFlags(y, flags.clusterName), handler: argv => { - clusterCmd.logger.debug("==== Running 'cluster delete' ===", {argv}) + clusterCmd.logger.debug("==== Running 'cluster delete' ===", { argv }) clusterCmd.delete(argv).then(r => { clusterCmd.logger.debug('==== Finished running `cluster delete`====') @@ -396,7 +396,7 @@ export class ClusterCommand extends BaseCommand { command: 'list', desc: 'List all clusters', handler: argv => { - clusterCmd.logger.debug("==== Running 'cluster list' ===", {argv}) + clusterCmd.logger.debug("==== Running 'cluster list' ===", { argv }) clusterCmd.showClusterList().then(r => { clusterCmd.logger.debug('==== Finished running `cluster list`====') @@ -413,7 +413,7 @@ export class ClusterCommand extends BaseCommand { desc: 'Get cluster info', builder: y => flags.setCommandFlags(y, flags.clusterName), handler: argv => { - clusterCmd.logger.debug("==== Running 'cluster info' ===", {argv}) + clusterCmd.logger.debug("==== Running 'cluster info' ===", { argv }) clusterCmd.getClusterInfo(argv).then(r => { clusterCmd.logger.debug('==== Finished running `cluster info`====') @@ -438,7 +438,7 @@ export class ClusterCommand extends BaseCommand { flags.deployCertManagerCRDs ), handler: argv => { - clusterCmd.logger.debug("==== Running 'cluster setup' ===", {argv}) + clusterCmd.logger.debug("==== Running 'cluster setup' ===", { argv }) clusterCmd.setup(argv).then(r => { clusterCmd.logger.debug('==== Finished running `cluster setup`====') @@ -458,7 +458,7 @@ export class ClusterCommand extends BaseCommand { flags.namespace ), handler: argv => { - clusterCmd.logger.debug("==== Running 'cluster reset' ===", {argv}) + clusterCmd.logger.debug("==== Running 'cluster reset' ===", { argv }) clusterCmd.reset(argv).then(r => { clusterCmd.logger.debug('==== Finished running `cluster reset`====') @@ -486,12 +486,12 @@ export class ClusterCommand extends BaseCommand { * @param certManagerCrdsEnabled a bool to denote whether to install cert manager CRDs * @returns {string} */ - prepareValuesArg(chartDir = flags.chartDirectory.definition.default, - prometheusStackEnabled = flags.deployPrometheusStack.definition.default, - minioEnabled = flags.deployMinio.definition.default, - envoyGatewayEnabled = flags.deployEnvoyGateway.definition.default, - certManagerEnabled = flags.deployCertManager.definition.default, - certManagerCrdsEnabled = flags.deployCertManagerCRDs.definition.default + prepareValuesArg (chartDir = flags.chartDirectory.definition.default, + prometheusStackEnabled = flags.deployPrometheusStack.definition.default, + minioEnabled = flags.deployMinio.definition.default, + envoyGatewayEnabled = flags.deployEnvoyGateway.definition.default, + certManagerEnabled = flags.deployCertManager.definition.default, + certManagerCrdsEnabled = flags.deployCertManagerCRDs.definition.default ) { let valuesArg = '' if (chartDir) { @@ -517,7 +517,7 @@ export class ClusterCommand extends BaseCommand { * @param chartDir local charts directory (default is empty) * @returns {Promise} */ - async prepareChartPath(chartDir = flags.chartDirectory.definition.default) { + async prepareChartPath (chartDir = flags.chartDirectory.definition.default) { let chartPath = 'full-stack-testing/fullstack-cluster-setup' if (chartDir) { chartPath = `${chartDir}/fullstack-cluster-setup` diff --git a/fullstack-network-manager/src/commands/prompts.mjs b/fullstack-network-manager/src/commands/prompts.mjs index d3a6993e2..283490aea 100644 --- a/fullstack-network-manager/src/commands/prompts.mjs +++ b/fullstack-network-manager/src/commands/prompts.mjs @@ -48,7 +48,7 @@ export async function promptNodeIdsArg (task, input) { input = await task.prompt(ListrEnquirerPromptAdapter).run({ type: 'input', default: 'node0,node1,node2', - message: 'Which nodes do you wish to setup? Use comma separated list:' + message: 'Enter list of node IDs (comma separated list):' }) } @@ -72,8 +72,8 @@ export async function promptReleaseTag (task, input) { if (!input) { input = await task.prompt(ListrEnquirerPromptAdapter).run({ type: 'text', - default: 'v0.42.5', - message: 'Which platform version do you wish to setup?' + default: flags.releaseTag.definition.default, + message: 'Enter release version:' }) } @@ -89,7 +89,7 @@ export async function promptCacheDir (task, input) { input = await task.prompt(ListrEnquirerPromptAdapter).run({ type: 'text', default: constants.FST_CACHE_DIR, - message: 'Which directory do you wish to use as local cache?' + message: 'Enter local cache directory path:' }) } @@ -121,7 +121,7 @@ export async function promptChainId (task, input) { input = await task.prompt(ListrEnquirerPromptAdapter).run({ type: 'text', default: flags.chainId.definition.default, - message: 'Which chain ID do you wish to use?' + message: 'Enter chain ID: ' }) } @@ -137,7 +137,7 @@ export async function promptChartDir (task, input) { input = await task.prompt(ListrEnquirerPromptAdapter).run({ type: 'text', default: flags.chartDirectory.definition.default, - message: 'Which charts directory do you wish to use?' + message: 'Enter local charts directory path: ' }) if (!fs.existsSync(input)) { @@ -147,7 +147,27 @@ export async function promptChartDir (task, input) { return input } catch (e) { - throw new FullstackTestingError(`input failed: ${flags.chainId.name}`, e) + throw new FullstackTestingError(`input failed: ${flags.chartDirectory.name}`, e) + } +} + +export async function promptValuesFile (task, input) { + try { + if (input && !fs.existsSync(input)) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + default: flags.valuesFile.definition.default, + message: 'Enter path to values.yaml: ' + }) + + if (!fs.existsSync(input)) { + throw new IllegalArgumentError('Invalid values.yaml file', input) + } + } + + return input + } catch (e) { + throw new FullstackTestingError(`input failed: ${flags.valuesFile.name}`, e) } } @@ -265,3 +285,83 @@ export async function promptDeployCertManagerCRDs (task, input) { throw new FullstackTestingError(`input failed: ${flags.deployCertManagerCRDs.name}`, e) } } + +export async function promptDeployMirrorNode (task, input) { + try { + if (input === undefined) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'toggle', + default: flags.deployMirrorNode.definition.default, + message: 'Would you like to deploy Hedera Mirror Node?' + }) + } + + return input + } catch (e) { + throw new FullstackTestingError(`input failed: ${flags.deployMirrorNode.name}`, e) + } +} + +export async function promptDeployHederaExplorer (task, input) { + try { + if (input === undefined) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'toggle', + default: flags.deployHederaExplorer.definition.default, + message: 'Would you like to deploy Hedera Explorer?' + }) + } + + return input + } catch (e) { + throw new FullstackTestingError(`input failed: ${flags.deployHederaExplorer.name}`, e) + } +} + +export async function promptOperatorId (task, input) { + try { + if (!input) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + default: flags.operatorId.definition.default, + message: 'Enter operator ID: ' + }) + } + + return input + } catch (e) { + throw new FullstackTestingError(`input failed: ${flags.operatorId.name}`, e) + } +} + +export async function promptOperatorKey (task, input) { + try { + if (!input) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + default: flags.operatorKey.definition.default, + message: 'Enter operator ID: ' + }) + } + + return input + } catch (e) { + throw new FullstackTestingError(`input failed: ${flags.operatorKey.name}`, e) + } +} + +export async function promptReplicaCount (task, input) { + try { + if (typeof input !== 'number') { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'number', + default: flags.replicaCount.definition.default, + message: 'How many replica do you want?' + }) + } + + return input + } catch (e) { + throw new FullstackTestingError(`input failed: ${flags.replicaCount.name}`, e) + } +} diff --git a/fullstack-network-manager/src/commands/relay.mjs b/fullstack-network-manager/src/commands/relay.mjs index 3c4e7d242..44d410910 100644 --- a/fullstack-network-manager/src/commands/relay.mjs +++ b/fullstack-network-manager/src/commands/relay.mjs @@ -1,14 +1,13 @@ -import chalk from 'chalk' -import { MissingArgumentError } from '../core/errors.mjs' +import { Listr } from 'listr2' +import { FullstackTestingError, MissingArgumentError } from '../core/errors.mjs' import { BaseCommand } from './base.mjs' import * as flags from './flags.mjs' import * as paths from 'path' import { constants } from '../core/index.mjs' +import * as prompts from './prompts.mjs' export class RelayCommand extends BaseCommand { - prepareValuesArg (config) { - const valuesFile = this.configManager.flagValue(config, flags.valuesFile) - + prepareValuesArg (valuesFile, nodeIDs, chainID, releaseTag, replicaCount, operatorID, operatorKey) { let valuesArg = '' if (valuesFile) { const valuesFiles = valuesFile.split(',') @@ -20,37 +19,31 @@ export class RelayCommand extends BaseCommand { valuesArg += ` --set config.MIRROR_NODE_URL=${constants.CHART_FST_DEPLOYMENT_NAME}-rest` - const chainID = this.configManager.flagValue(config, flags.chainId) if (chainID) { valuesArg += ` --set config.CHAIN_ID=${chainID}` } - const releaseTag = this.configManager.flagValue(config, flags.releaseTag) if (releaseTag) { valuesArg += ` --set image.tag=${releaseTag.replace(/^v/, '')}` } - const replicaCount = this.configManager.flagValue(config, flags.replicaCount) if (replicaCount) { valuesArg += ` --set replicaCount=${replicaCount}` } - const operatorId = this.configManager.flagValue(config, flags.operatorId) - if (operatorId) { - valuesArg += ` --set config.OPERATOR_ID_MAIN=${operatorId}` + if (operatorID) { + valuesArg += ` --set config.OPERATOR_ID_MAIN=${operatorID}` } - const operatorKey = this.configManager.flagValue(config, flags.operatorKey) if (operatorKey) { valuesArg += ` --set config.OPERATOR_KEY_MAIN=${operatorKey}` } - const nodeIDs = this.configManager.flagValue(config, flags.nodeIDs) if (!nodeIDs) { throw new MissingArgumentError('Node IDs must be specified') } - nodeIDs.split(',').forEach(nodeID => { + nodeIDs.forEach(nodeID => { const networkKey = `network-${nodeID.trim()}-0-svc:50211` valuesArg += ` --set config.HEDERA_NETWORK.${networkKey}=0.0.3` }) @@ -58,14 +51,13 @@ export class RelayCommand extends BaseCommand { return valuesArg } - prepareReleaseName (config) { - const nodeIDs = this.configManager.flagValue(config, flags.nodeIDs) + prepareReleaseName (nodeIDs = []) { if (!nodeIDs) { throw new MissingArgumentError('Node IDs must be specified') } let releaseName = 'relay' - nodeIDs.split(',').forEach(nodeID => { + nodeIDs.forEach(nodeID => { releaseName += `-${nodeID}` }) @@ -73,40 +65,143 @@ export class RelayCommand extends BaseCommand { } async install (argv) { + const self = this + const tasks = new Listr([ + { + title: 'Initialize', + task: async (ctx, task) => { + const cachedConfig = await self.configManager.setupConfig(argv) + self.logger.debug('Setup cached config', { cachedConfig, argv }) + + // extract config values + const valuesFile = self.configManager.flagValue(cachedConfig, flags.valuesFile) + const nodeIds = self.configManager.flagValue(cachedConfig, flags.nodeIDs) + const chainId = self.configManager.flagValue(cachedConfig, flags.chainId) + const releaseTag = self.configManager.flagValue(cachedConfig, flags.releaseTag) + const replicaCount = self.configManager.flagValue(cachedConfig, flags.replicaCount) + const operatorId = self.configManager.flagValue(cachedConfig, flags.operatorId) + const operatorKey = self.configManager.flagValue(cachedConfig, flags.operatorKey) + + const namespace = self.configManager.flagValue(cachedConfig, flags.namespace) + const chartDir = self.configManager.flagValue(cachedConfig, flags.chartDirectory) + + // prompt if inputs are empty and set it in the context + const namespaces = await self.kubectl.getNamespace('--no-headers', '-o name') + ctx.config = { + chartDir: await prompts.promptChartDir(task, chartDir), + namespace: await prompts.promptSelectNamespaceArg(task, namespace, namespaces), + valuesFile: await prompts.promptValuesFile(task, valuesFile), + nodeIds: await prompts.promptNodeIdsArg(task, nodeIds), + chainId: await prompts.promptChainId(task, chainId), + releaseTag: await prompts.promptReleaseTag(task, releaseTag), + replicaCount: await prompts.promptReplicaCount(task, replicaCount), + operatorId: await prompts.promptOperatorId(task, operatorId), + operatorKey: await prompts.promptOperatorId(task, operatorKey) + } + + self.logger.debug('Finished prompts', { ctx }) + + ctx.releaseName = this.prepareReleaseName(ctx.config.nodeIds) + ctx.isChartInstalled = await this.chartManager.isChartInstalled(ctx.config.namespace, ctx.releaseName) + + self.logger.debug('Finished ctx initialization', { ctx }) + } + }, + { + title: 'Prepare chart values', + task: async (ctx, _) => { + ctx.chartPath = await this.prepareChartPath(ctx.config.chartDir, constants.CHART_JSON_RPC_RELAY_REPO_NAME, constants.CHART_JSON_RPC_RELAY_NAME) + ctx.valuesArg = this.prepareValuesArg( + ctx.config.valuesFile, + ctx.config.nodeIds, + ctx.config.chainId, + ctx.config.releaseTag, + ctx.config.replicaCount, + ctx.config.operatorId, + ctx.config.operatorKey + ) + }, + skip: (ctx, _) => ctx.isChartInstalled + }, + { + title: 'Install JSON RPC Relay', + task: async (ctx, _) => { + const namespace = ctx.config.namespace + const releaseName = ctx.releaseName + const chartPath = ctx.chartPath + const valuesArg = ctx.valuesArg + + await this.chartManager.install(namespace, releaseName, chartPath, '', valuesArg) + + await this.kubectl.wait('pod', + '--for=condition=ready', + '-l app=hedera-json-rpc-relay', + `-l app.kubernetes.io/instance=${releaseName}`, + '--timeout=900s' + ) + + this.logger.showList('Deployed Relays', await self.chartManager.getInstalledCharts(namespace)) + } + } + ]) + try { - const config = await this.configManager.setupConfig(argv) - const namespace = this.configManager.flagValue(config, flags.namespace) - const valuesArg = this.prepareValuesArg(config) - const chartPath = await this.prepareChartPath(config, constants.CHART_JSON_RPC_RELAY_REPO_NAME, constants.CHART_JSON_RPC_RELAY_NAME) - const releaseName = this.prepareReleaseName(config) - - await this.chartManager.install(namespace, releaseName, chartPath, '', valuesArg) - - this.logger.showUser(chalk.cyan(`> waiting for ${releaseName} pods to be ready...`)) - await this.kubectl.wait('pod', - '--for=condition=ready', - '-l app=hedera-json-rpc-relay', - `-l app.kubernetes.io/instance=${releaseName}`, - '--timeout=900s' - ) - this.logger.showUser(chalk.green('OK'), `${releaseName} pods are ready`) - this.logger.showList('Deployed Relays', await this.chartManager.getInstalledCharts(namespace)) - return true + await tasks.run() } catch (e) { - this.logger.showUserError(e) + throw new FullstackTestingError('Error installing relays', e) } - return false + return true } async uninstall (argv) { - const config = await this.configManager.setupConfig(argv) - const namespace = this.configManager.flagValue(config, flags.namespace) - const releaseName = this.prepareReleaseName(config) - return await this.chartManager.uninstall(namespace, releaseName) + const self = this + + const tasks = new Listr([ + { + title: 'Initialize', + task: async (ctx, task) => { + const cachedConfig = await self.configManager.setupConfig(argv) + self.logger.debug('Setup cached config', { cachedConfig, argv }) + + // extract config values + const nodeIds = self.configManager.flagValue(cachedConfig, flags.nodeIDs) + const namespace = self.configManager.flagValue(cachedConfig, flags.namespace) + + // prompt if inputs are empty and set it in the context + const namespaces = await self.kubectl.getNamespace('--no-headers', '-o name') + ctx.config = { + namespace: await prompts.promptSelectNamespaceArg(task, namespace, namespaces), + nodeIds: await prompts.promptNodeIdsArg(task, nodeIds) + } + + ctx.config.releaseName = this.prepareReleaseName(ctx.config.nodeIds) + self.logger.debug('Finished ctx initialization', { ctx }) + } + }, + { + title: 'Install JSON RPC Relay', + task: async (ctx, _) => { + const namespace = ctx.config.namespace + const releaseName = ctx.config.releaseName + + await this.chartManager.uninstall(namespace, releaseName) + + this.logger.showList('Deployed Relays', await self.chartManager.getInstalledCharts(namespace)) + } + } + ]) + + try { + await tasks.run() + } catch (e) { + throw new FullstackTestingError('Error uninstalling relays', e) + } + + return true } - static getCommandDefinition (chartCmd) { + static getCommandDefinition (relayCmd) { return { command: 'relay', desc: 'Manage JSON RPC relays', @@ -129,13 +224,15 @@ export class RelayCommand extends BaseCommand { ) }, handler: argv => { - chartCmd.logger.debug("==== Running 'chart install' ===") - chartCmd.logger.debug(argv) + relayCmd.logger.debug("==== Running 'chart install' ===", { argv }) - chartCmd.install(argv).then(r => { - chartCmd.logger.debug('==== Finished running `chart install`====') + relayCmd.install(argv).then(r => { + relayCmd.logger.debug('==== Finished running `chart install`====') if (!r) process.exit(1) + }).catch(err => { + relayCmd.logger.showUserError(err) + process.exit(1) }) } }) @@ -147,11 +244,11 @@ export class RelayCommand extends BaseCommand { flags.nodeIDs ), handler: argv => { - chartCmd.logger.debug("==== Running 'chart uninstall' ===") - chartCmd.logger.debug(argv) + relayCmd.logger.debug("==== Running 'chart uninstall' ===", { argv }) + relayCmd.logger.debug(argv) - chartCmd.uninstall(argv).then(r => { - chartCmd.logger.debug('==== Finished running `chart uninstall`====') + relayCmd.uninstall(argv).then(r => { + relayCmd.logger.debug('==== Finished running `chart uninstall`====') if (!r) process.exit(1) }) diff --git a/fullstack-network-manager/src/core/chart_manager.mjs b/fullstack-network-manager/src/core/chart_manager.mjs index 520ea8e30..1e8f0b3d8 100644 --- a/fullstack-network-manager/src/core/chart_manager.mjs +++ b/fullstack-network-manager/src/core/chart_manager.mjs @@ -68,19 +68,17 @@ export class ChartManager { namespaceArg = `-n ${namespaceName}` } - this.logger.showUser(chalk.cyan('> installing chart:'), chalk.yellow(`${chartPath}`)) + this.logger.debug(`> installing chart:${chartPath}`) await this.helm.install(`${chartName} ${chartPath} ${versionArg} ${namespaceArg} ${valuesArg}`) - this.logger.showUser(chalk.green('OK'), 'chart is installed:', chalk.yellow(`${chartName} (${chartPath})`)) + this.logger.debug(`OK: chart is installed: ${chartName} (${chartPath})`) } else { - this.logger.showUser(chalk.green('OK'), 'chart is already installed:', chalk.yellow(`${chartName} (${chartPath})`)) + this.logger.debug(`OK: chart is already installed:${chartName} (${chartPath})`) } - - return true } catch (e) { - this.logger.showUserError(e) + throw new FullstackTestingError(`failed to install chart ${chartName}: ${e.message}`, e) } - return false + return true } async isChartInstalled (namespaceName, chartName) { @@ -96,22 +94,20 @@ export class ChartManager { async uninstall (namespaceName, chartName) { try { - this.logger.showUser(chalk.cyan('> checking chart release:'), chalk.yellow(`${chartName}`)) + this.logger.debug(`> checking chart release: ${chartName}`) const isInstalled = await this.isChartInstalled(namespaceName, chartName) if (isInstalled) { - this.logger.showUser(chalk.cyan('> uninstalling chart release:'), chalk.yellow(`${chartName}`)) + this.logger.debug(`uninstalling chart release: ${chartName}`) await this.helm.uninstall(`-n ${namespaceName} ${chartName}`) - this.logger.showUser(chalk.green('OK'), 'chart release is uninstalled:', chalk.yellow(chartName)) + this.logger.debug(`OK: chart release is uninstalled: ${chartName}`) } else { - this.logger.showUser(chalk.green('OK'), 'chart release is already uninstalled:', chalk.yellow(chartName)) + this.logger.debug(`OK: chart release is already uninstalled: ${chartName}`) } - - return true } catch (e) { - this.logger.showUserError(e) + throw new FullstackTestingError(`failed to uninstall chart ${chartName}: ${e.message}`, e) } - return false + return true } async upgrade (namespaceName, chartName, chartPath, valuesArg = '') { @@ -119,12 +115,10 @@ export class ChartManager { this.logger.showUser(chalk.cyan('> upgrading chart:'), chalk.yellow(`${chartName}`)) await this.helm.upgrade(`-n ${namespaceName} ${chartName} ${chartPath} ${valuesArg}`) this.logger.showUser(chalk.green('OK'), `chart '${chartName}' is upgraded`) - - return true } catch (e) { - this.logger.showUserError(e) + throw new FullstackTestingError(`failed to upgrade chart ${chartName}: ${e.message}`, e) } - return false + return true } }