diff --git a/fullstack-network-manager/package-lock.json b/fullstack-network-manager/package-lock.json index 1c21c98db..5b0bd6798 100644 --- a/fullstack-network-manager/package-lock.json +++ b/fullstack-network-manager/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hashgraph/fullstack-network-manager", - "version": "0.12.0", + "version": "0.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hashgraph/fullstack-network-manager", - "version": "0.12.0", + "version": "0.13.0", "license": "Apache2.0", "os": [ "darwin", @@ -15,6 +15,7 @@ "dependencies": { "adm-zip": "^0.5.10", "chalk": "^5.3.0", + "dotenv": "^16.3.1", "esm": "^3.2.25", "figlet": "^1.6.0", "got": "^13.0.0", @@ -2962,6 +2963,17 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/fullstack-network-manager/package.json b/fullstack-network-manager/package.json index 8019100f6..71a840fe6 100644 --- a/fullstack-network-manager/package.json +++ b/fullstack-network-manager/package.json @@ -26,6 +26,7 @@ "dependencies": { "adm-zip": "^0.5.10", "chalk": "^5.3.0", + "dotenv": "^16.3.1", "esm": "^3.2.25", "figlet": "^1.6.0", "got": "^13.0.0", diff --git a/fullstack-network-manager/src/commands/base.mjs b/fullstack-network-manager/src/commands/base.mjs index 984a52625..931f20662 100644 --- a/fullstack-network-manager/src/commands/base.mjs +++ b/fullstack-network-manager/src/commands/base.mjs @@ -1,7 +1,9 @@ 'use strict' +import { MissingArgumentError } from '../core/errors.mjs' import * as core from '../core/index.mjs' import chalk from 'chalk' import { ShellRunner } from '../core/shell_runner.mjs' +import * as flags from './flags.mjs' export class BaseCommand extends ShellRunner { async checkDep (cmd) { @@ -71,6 +73,21 @@ export class BaseCommand extends ShellRunner { return true } + async prepareChartPath (config, chartRepo, chartName) { + if (!config) throw new MissingArgumentError('config is required') + 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) + return chartPath + } + + return `${chartRepo}/${chartName}` + } + constructor (opts) { if (!opts || !opts.logger) throw new Error('An instance of core/Logger is required') if (!opts || !opts.kind) throw new Error('An instance of core/Kind is required') diff --git a/fullstack-network-manager/src/commands/chart.mjs b/fullstack-network-manager/src/commands/chart.mjs index 7a2a3c99e..9d81b664e 100644 --- a/fullstack-network-manager/src/commands/chart.mjs +++ b/fullstack-network-manager/src/commands/chart.mjs @@ -1,49 +1,49 @@ import chalk from 'chalk' +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' export class ChartCommand extends BaseCommand { - prepareValuesArg (argv, config) { - const { valuesFile, mirrorNode, hederaExplorer } = argv - + prepareValuesFiles (valuesFile) { let valuesArg = '' - const chartDir = this.configManager.flagValue(config, flags.chartDirectory) - if (chartDir) { - valuesArg = `-f ${chartDir}/fullstack-deployment/values.yaml` - } - if (valuesFile) { - valuesArg += `--values ${valuesFile}` + const valuesFiles = valuesFile.split(',') + valuesFiles.forEach(vf => { + const vfp = paths.resolve(vf) + valuesArg += ` --values ${vfp}` + }) } - valuesArg += ` --set hedera-mirror-node.enabled=${mirrorNode} --set hedera-explorer.enabled=${hederaExplorer}` - return valuesArg } - async prepareChartPath (config) { + 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) + + let valuesArg = '' const chartDir = this.configManager.flagValue(config, flags.chartDirectory) - let chartPath = 'full-stack-testing/fullstack-deployment' if (chartDir) { - chartPath = `${chartDir}/fullstack-deployment` - await this.helm.dependency('update', chartPath) + valuesArg = `-f ${chartDir}/fullstack-deployment/values.yaml` } - return chartPath - } + valuesArg += this.prepareValuesFiles(valuesFile) - async install (argv) { - try { - const namespace = argv.namespace + valuesArg += ` --set hedera-mirror-node.enabled=${deployMirrorNode} --set hedera-explorer.enabled=${deployHederaExplorer}` - const config = await this.configManager.setupConfig(argv) - const valuesArg = this.prepareValuesArg(argv, config) - const chartPath = await this.prepareChartPath(config) + return valuesArg + } - await this.chartManager.install(namespace, constants.FST_CHART_DEPLOYMENT_NAME, chartPath, config.version, 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) - this.logger.showList('charts', await this.chartManager.getInstalledCharts(namespace)) + 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', @@ -52,7 +52,19 @@ export class ChartCommand extends BaseCommand { '--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 install (argv) { + try { + const config = await this.configManager.setupConfig(argv) + const namespace = this.configManager.flagValue(config, flags.namespace) + await this.installFSTChart(config) + + this.logger.showList('Deployed Charts', await this.chartManager.getInstalledCharts(namespace)) return true } catch (e) { this.logger.showUserError(e) @@ -64,7 +76,7 @@ export class ChartCommand extends BaseCommand { async uninstall (argv) { const namespace = argv.namespace - return await this.chartManager.uninstall(namespace, constants.FST_CHART_DEPLOYMENT_NAME) + return await this.chartManager.uninstall(namespace, constants.CHART_FST_DEPLOYMENT_NAME) } async upgrade (argv) { @@ -74,7 +86,7 @@ export class ChartCommand extends BaseCommand { const valuesArg = this.prepareValuesArg(argv, config) const chartPath = await this.prepareChartPath(config) - return await this.chartManager.upgrade(namespace, constants.FST_CHART_DEPLOYMENT_NAME, chartPath, valuesArg) + return await this.chartManager.upgrade(namespace, constants.CHART_FST_DEPLOYMENT_NAME, chartPath, valuesArg) } static getCommandDefinition (chartCmd) { @@ -86,13 +98,16 @@ export class ChartCommand extends BaseCommand { .command({ command: 'install', desc: 'Install FST network deployment chart', - builder: y => flags.setCommandFlags(y, - flags.namespace, - flags.deployMirrorNode, - flags.deployHederaExplorer, - flags.valuesFile, - flags.chartDirectory - ), + builder: y => { + flags.setCommandFlags(y, + flags.namespace, + flags.deployMirrorNode, + flags.deployHederaExplorer, + flags.deployJsonRpcRelay, + flags.valuesFile, + flags.chartDirectory + ) + }, handler: argv => { chartCmd.logger.debug("==== Running 'chart install' ===") chartCmd.logger.debug(argv) diff --git a/fullstack-network-manager/src/commands/cluster.mjs b/fullstack-network-manager/src/commands/cluster.mjs index 1faf3a761..35324d80a 100644 --- a/fullstack-network-manager/src/commands/cluster.mjs +++ b/fullstack-network-manager/src/commands/cluster.mjs @@ -177,7 +177,7 @@ export class ClusterCommand extends BaseCommand { const valuesArg = this.prepareValuesArg(config, argv.prometheusStack, argv.minio, argv.envoyGateway, argv.certManager, argv.certManagerCrds) this.logger.showUser(chalk.cyan('> setting up cluster:'), chalk.yellow(`${chartPath}`, chalk.yellow(valuesArg))) - await this.chartManager.install(namespace, constants.FST_CHART_SETUP_NAME, chartPath, config.version, valuesArg) + await this.chartManager.install(namespace, constants.CHART_FST_SETUP_NAME, chartPath, config.version, valuesArg) await this.showInstalledChartList(namespace) return true @@ -203,7 +203,7 @@ export class ClusterCommand extends BaseCommand { const valuesArg = this.prepareValuesArg(config, argv.prometheusStack, argv.minio, argv.envoyGateway, argv.certManager, argv.certManagerCrds) this.logger.showUser(chalk.cyan('> resetting cluster:'), chalk.yellow(`${chartPath}`, chalk.yellow(valuesArg))) - await this.chartManager.uninstall(namespace, constants.FST_CHART_SETUP_NAME, chartPath, config.version, valuesArg) + await this.chartManager.uninstall(namespace, constants.CHART_FST_SETUP_NAME, chartPath, config.version, valuesArg) await this.showInstalledChartList(namespace) diff --git a/fullstack-network-manager/src/commands/flags.mjs b/fullstack-network-manager/src/commands/flags.mjs index d52d8c90f..9e0f82996 100644 --- a/fullstack-network-manager/src/commands/flags.mjs +++ b/fullstack-network-manager/src/commands/flags.mjs @@ -56,7 +56,7 @@ export const deployHederaExplorer = { export const valuesFile = { name: 'values-file', definition: { - describe: 'Helm chart values file [ to override defaults ]', + describe: 'Comma separated chart values files', default: '', alias: 'f', type: 'string' @@ -112,10 +112,20 @@ export const deployCertManagerCRDs = { } } -export const platformReleaseTag = { +export const deployJsonRpcRelay = { + name: 'json-rpc-relay', + definition: { + describe: 'Deploy JSON RPC Relay', + default: false, + alias: 'j', + type: 'boolean' + } +} + +export const releaseTag = { name: 'release-tag', definition: { - describe: 'Platform release tag (e.g. v0.42.4, fetch build-.zip from https://builds.hedera.com)', + describe: 'Release tag to be used (e.g. v0.42.5)', default: '', alias: 't', type: 'string' @@ -125,7 +135,7 @@ export const platformReleaseTag = { export const platformReleaseDir = { name: 'release-dir', definition: { - describe: 'Platform release cache dir (containing release directories named as v.. e.g. v0.42)', + describe: 'Platform release cache (containing release directories named as v.. e.g. v0.42)', default: core.constants.FST_CACHE_DIR, alias: 'd', type: 'string' @@ -162,19 +172,63 @@ export const chartDirectory = { } } +export const replicaCount = { + name: 'replica-count', + definition: { + describe: 'Replica count', + default: 1, + alias: '', + type: 'number' + } +} + +export const chainId = { + name: 'chain-id', + definition: { + describe: 'Chain ID', + default: '298', // Ref: https://github.com/hashgraph/hedera-json-rpc-relay#configuration + type: 'string' + } +} + +// Ref: https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/configuration.md +export const operatorId = { + name: 'operator-id', + definition: { + describe: 'Operator ID', + default: '0.0.2', + type: 'string' + } +} + +// Ref: https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/configuration.md +export const operatorKey = { + name: 'operator-key', + definition: { + describe: 'Operator Key', + default: '302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137', + type: 'string' + } +} + export const allFlags = [ clusterName, namespace, deployMirrorNode, deployHederaExplorer, + deployJsonRpcRelay, valuesFile, deployPrometheusStack, deployMinio, deployEnvoyGateway, deployCertManagerCRDs, - platformReleaseTag, + releaseTag, platformReleaseDir, nodeIDs, force, - chartDirectory + chartDirectory, + replicaCount, + chainId, + operatorId, + operatorKey ] diff --git a/fullstack-network-manager/src/commands/index.mjs b/fullstack-network-manager/src/commands/index.mjs index 2dc083a6a..fa96ee0fb 100644 --- a/fullstack-network-manager/src/commands/index.mjs +++ b/fullstack-network-manager/src/commands/index.mjs @@ -2,6 +2,7 @@ import { ClusterCommand } from './cluster.mjs' import { InitCommand } from './init.mjs' import { ChartCommand } from './chart.mjs' import { NodeCommand } from './node.mjs' +import { RelayCommand } from './relay.mjs' /* * Return a list of Yargs command builder to be exposed through CLI @@ -12,12 +13,14 @@ function Initialize (opts) { const clusterCmd = new ClusterCommand(opts) const chartCmd = new ChartCommand(opts) const nodeCmd = new NodeCommand(opts) + const relayCmd = new RelayCommand(opts) return [ InitCommand.getCommandDefinition(initCmd), ClusterCommand.getCommandDefinition(clusterCmd), ChartCommand.getCommandDefinition(chartCmd), - NodeCommand.getCommandDefinition(nodeCmd) + NodeCommand.getCommandDefinition(nodeCmd), + RelayCommand.getCommandDefinition(relayCmd) ] } diff --git a/fullstack-network-manager/src/commands/init.mjs b/fullstack-network-manager/src/commands/init.mjs index ca43d67b2..617d6e5b4 100644 --- a/fullstack-network-manager/src/commands/init.mjs +++ b/fullstack-network-manager/src/commands/init.mjs @@ -58,7 +58,7 @@ export class InitCommand extends BaseCommand { this.logger.showUser(chalk.green('OK: All required dependencies are found: %s'), chalk.yellow(deps)) const repoURLs = await this.chartManager.setup() - this.logger.showUser(chalk.green('OK: Chart repositories are initialized'), chalk.yellow(repoURLs)) + this.logger.showList('Chart Repository', repoURLs) return status } catch (e) { diff --git a/fullstack-network-manager/src/commands/node.mjs b/fullstack-network-manager/src/commands/node.mjs index 7b5d0cf1f..7fa243631 100644 --- a/fullstack-network-manager/src/commands/node.mjs +++ b/fullstack-network-manager/src/commands/node.mjs @@ -72,12 +72,14 @@ export class NodeCommand extends BaseCommand { const self = this if (!argv.releaseTag && !argv.releaseDir) throw new MissingArgumentError('release-tag or release-dir argument is required') - const namespace = argv.namespace - const force = argv.force - const releaseTag = argv.releaseTag - const releaseDir = argv.releaseDir - try { + const config = await this.configManager.setupConfig(argv) + const namespace = this.configManager.flagValue(config, flags.namespace) + const force = this.configManager.flagValue(config, flags.force) + const releaseTag = this.configManager.flagValue(config, flags.releaseTag) + const releaseDir = this.configManager.flagValue(config, flags.platformReleaseDir) + const chainId = this.configManager.flagValue(config, flags.chainId) + self.logger.showUser(constants.LOG_GROUP_DIVIDER) const releasePrefix = Templates.prepareReleasePrefix(releaseTag) @@ -100,7 +102,7 @@ export class NodeCommand extends BaseCommand { self.logger.showUser(chalk.green('OK'), `Platform package: ${buildZipFile}`) // prepare staging - await this.plaformInstaller.prepareStaging(nodeIDs, stagingDir, releaseTag, force) + await this.plaformInstaller.prepareStaging(nodeIDs, stagingDir, releaseTag, force, chainId) // setup for (const podName of podNames) { @@ -173,9 +175,10 @@ export class NodeCommand extends BaseCommand { builder: y => flags.setCommandFlags(y, flags.namespace, flags.nodeIDs, - flags.platformReleaseTag, + flags.releaseTag, flags.platformReleaseDir, - flags.force + flags.force, + flags.chainId ), handler: argv => { nodeCmd.logger.debug("==== Running 'node setup' ===") diff --git a/fullstack-network-manager/src/commands/relay.mjs b/fullstack-network-manager/src/commands/relay.mjs new file mode 100644 index 000000000..3c4e7d242 --- /dev/null +++ b/fullstack-network-manager/src/commands/relay.mjs @@ -0,0 +1,164 @@ +import chalk from 'chalk' +import { 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' + +export class RelayCommand extends BaseCommand { + prepareValuesArg (config) { + const valuesFile = this.configManager.flagValue(config, flags.valuesFile) + + let valuesArg = '' + if (valuesFile) { + const valuesFiles = valuesFile.split(',') + valuesFiles.forEach(vf => { + const vfp = paths.resolve(vf) + valuesArg += ` --values ${vfp}` + }) + } + + 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}` + } + + 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 => { + const networkKey = `network-${nodeID.trim()}-0-svc:50211` + valuesArg += ` --set config.HEDERA_NETWORK.${networkKey}=0.0.3` + }) + + return valuesArg + } + + prepareReleaseName (config) { + const nodeIDs = this.configManager.flagValue(config, flags.nodeIDs) + if (!nodeIDs) { + throw new MissingArgumentError('Node IDs must be specified') + } + + let releaseName = 'relay' + nodeIDs.split(',').forEach(nodeID => { + releaseName += `-${nodeID}` + }) + + return releaseName + } + + async install (argv) { + 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 + } catch (e) { + this.logger.showUserError(e) + } + + return false + } + + 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) + } + + static getCommandDefinition (chartCmd) { + return { + command: 'relay', + desc: 'Manage JSON RPC relays', + builder: yargs => { + return yargs + .command({ + command: 'install', + desc: 'Install a JSON RPC relay', + builder: y => { + flags.setCommandFlags(y, + flags.namespace, + flags.valuesFile, + flags.chartDirectory, + flags.replicaCount, + flags.chainId, + flags.nodeIDs, + flags.releaseTag, + flags.operatorId, + flags.operatorKey + ) + }, + handler: argv => { + chartCmd.logger.debug("==== Running 'chart install' ===") + chartCmd.logger.debug(argv) + + chartCmd.install(argv).then(r => { + chartCmd.logger.debug('==== Finished running `chart install`====') + + if (!r) process.exit(1) + }) + } + }) + .command({ + command: 'uninstall', + desc: 'Uninstall JSON RPC relay', + builder: y => flags.setCommandFlags(y, + flags.namespace, + flags.nodeIDs + ), + handler: argv => { + chartCmd.logger.debug("==== Running 'chart uninstall' ===") + chartCmd.logger.debug(argv) + + chartCmd.uninstall(argv).then(r => { + chartCmd.logger.debug('==== Finished running `chart uninstall`====') + + if (!r) process.exit(1) + }) + } + }) + .demandCommand(1, 'Select a chart command') + } + } + } +} diff --git a/fullstack-network-manager/src/core/chart_manager.mjs b/fullstack-network-manager/src/core/chart_manager.mjs index 55ef43681..44e989226 100644 --- a/fullstack-network-manager/src/core/chart_manager.mjs +++ b/fullstack-network-manager/src/core/chart_manager.mjs @@ -1,4 +1,4 @@ -import { constants } from './constants.mjs' +import { constants } from './index.mjs' import chalk from 'chalk' import { FullstackTestingError } from './errors.mjs' @@ -12,15 +12,15 @@ export class ChartManager { } /** - * Setup chart repositories - * - * This must be invoked before calling other methods - * - * @param repoURLs a map of name and chart repository URLs - * @param force whether or not to update the repo - * @returns {Promise} - */ - async setup (repoURLs = new Map().set('full-stack-testing', constants.FST_CHART_REPO_URL), force = true) { + * Setup chart repositories + * + * This must be invoked before calling other methods + * + * @param repoURLs a map of name and chart repository URLs + * @param force whether or not to update the repo + * @returns {Promise} + */ + async setup (repoURLs = constants.DEFAULT_CHART_REPO, force = true) { try { let forceUpdateArg = '' if (force) { @@ -41,12 +41,12 @@ export class ChartManager { } /** - * List available clusters - * @returns {Promise} - */ + * List available clusters + * @returns {Promise} + */ async getInstalledCharts (namespaceName) { try { - return await this.helm.list(`-n ${namespaceName}`, '--no-headers | awk \'{print $9}\'') + return await this.helm.list(`-n ${namespaceName}`, '--no-headers | awk \'{print $1 " [" $9"]"}\'') } catch (e) { this.logger.showUserError(e) } @@ -58,11 +58,21 @@ export class ChartManager { try { const isInstalled = await this.isChartInstalled(namespaceName, chartName) if (!isInstalled) { + let versionArg = '' + if (version) { + versionArg = `--version ${version}` + } + + let namespaceArg = '' + if (namespaceName) { + namespaceArg = `-n ${namespaceName}` + } + this.logger.showUser(chalk.cyan('> installing chart:'), chalk.yellow(`${chartPath}`)) - await this.helm.install(`${chartName} ${chartPath} --version ${version} -n ${namespaceName} ${valuesArg}`) - this.logger.showUser(chalk.green('OK'), `chart '${chartPath}' is installed`) + await this.helm.install(`${chartName} ${chartPath} ${versionArg} ${namespaceArg} ${valuesArg}`) + this.logger.showUser(chalk.green('OK'), 'chart is installed:', chalk.yellow(`${chartName} (${chartPath})`)) } else { - this.logger.showUser(chalk.green('OK'), `chart '${chartPath}' is already installed`) + this.logger.showUser(chalk.green('OK'), 'chart is already installed:', chalk.yellow(`${chartName} (${chartPath})`)) } return true @@ -76,7 +86,8 @@ export class ChartManager { async isChartInstalled (namespaceName, chartName) { const charts = await this.getInstalledCharts(namespaceName) for (const item of charts) { - if (item.startsWith(chartName)) return true + const n = item.split(' [') + if (chartName === n[0]) return true } return false @@ -84,14 +95,14 @@ export class ChartManager { async uninstall (namespaceName, chartName) { try { - this.logger.showUser(chalk.cyan('> checking chart:'), chalk.yellow(`${chartName}`)) + this.logger.showUser(chalk.cyan('> checking chart release:'), chalk.yellow(`${chartName}`)) const isInstalled = await this.isChartInstalled(namespaceName, chartName) if (isInstalled) { - this.logger.showUser(chalk.cyan('> uninstalling chart:'), chalk.yellow(`${chartName}`)) + this.logger.showUser(chalk.cyan('> uninstalling chart release:'), chalk.yellow(`${chartName}`)) await this.helm.uninstall(`-n ${namespaceName} ${chartName}`) - this.logger.showUser(chalk.green('OK'), `chart '${chartName}' is uninstalled`) + this.logger.showUser(chalk.green('OK'), 'chart release is uninstalled:', chalk.yellow(chartName)) } else { - this.logger.showUser(chalk.green('OK'), `chart '${chartName}' is already uninstalled`) + this.logger.showUser(chalk.green('OK'), 'chart release is already uninstalled:', chalk.yellow(chartName)) } return true diff --git a/fullstack-network-manager/src/core/config_manager.mjs b/fullstack-network-manager/src/core/config_manager.mjs index 906f2b00d..46d898d87 100644 --- a/fullstack-network-manager/src/core/config_manager.mjs +++ b/fullstack-network-manager/src/core/config_manager.mjs @@ -1,6 +1,6 @@ import fs from 'fs' import { FullstackTestingError, MissingArgumentError } from './errors.mjs' -import { constants } from './constants.mjs' +import { constants } from './index.mjs' import { Logger } from './logging.mjs' import * as flags from '../commands/flags.mjs' import * as paths from 'path' @@ -48,9 +48,10 @@ export class ConfigManager { * * @param opts object containing various config related fields (e.g. argv) * @param reset if we should reset old values + * @param flagList list of flags to be processed * @returns {Promise} */ - async setupConfig (opts, reset = false) { + async setupConfig (opts, reset = false, flagList = flags.allFlags) { const self = this try { @@ -73,7 +74,7 @@ export class ConfigManager { // extract flags from argv if (opts) { - flags.allFlags.forEach(flag => { + flagList.forEach(flag => { if (opts && opts[flag.name] !== undefined) { let val = opts[flag.name] if (val && flag.name === flags.chartDirectory.name) { diff --git a/fullstack-network-manager/src/core/constants.mjs b/fullstack-network-manager/src/core/constants.mjs index 9ad559015..3045021f8 100644 --- a/fullstack-network-manager/src/core/constants.mjs +++ b/fullstack-network-manager/src/core/constants.mjs @@ -2,40 +2,53 @@ import { dirname, normalize } from 'path' import { fileURLToPath } from 'url' import chalk from 'chalk' -// directory of this fle -const CUR_FILE_DIR = dirname(fileURLToPath(import.meta.url)) -const USER = `${process.env.USER}` -const FST_HOME_DIR = `${process.env.HOME}/.fsnetman` -const HGCAPP_DIR = '/opt/hgcapp' +// -------------------- fsnetman related constants --------------------------------------------------------------------- +export const CUR_FILE_DIR = dirname(fileURLToPath(import.meta.url)) +export const USER = `${process.env.USER}` +export const FST_HOME_DIR = `${process.env.HOME}/.fsnetman` +export const FST_LOGS_DIR = `${FST_HOME_DIR}/logs` +export const FST_CACHE_DIR = `${FST_HOME_DIR}/cache` +export const CLUSTER_NAME = 'fst' +export const RELEASE_NAME = 'fst' +export const NAMESPACE_NAME = `fst-${USER}` +export const HELM = 'helm' +export const KIND = 'kind' +export const KUBECTL = 'kubectl' +export const CWD = process.cwd() +export const FST_CONFIG_FILE = `${FST_HOME_DIR}/fsnetman.config` +export const RESOURCES_DIR = normalize(CUR_FILE_DIR + '/../../resources') -export const constants = { - USER: `${USER}`, - CLUSTER_NAME: 'fst', - RELEASE_NAME: 'fst', - NAMESPACE_NAME: `fst-${USER}`, - HELM: 'helm', - KIND: 'kind', - KUBECTL: 'kubectl', - CWD: process.cwd(), - FST_HOME_DIR, - FST_LOGS_DIR: `${FST_HOME_DIR}/logs`, - FST_CACHE_DIR: `${FST_HOME_DIR}/cache`, - FST_CONFIG_FILE: `${FST_HOME_DIR}/fsnetman.config`, - RESOURCES_DIR: normalize(CUR_FILE_DIR + '/../../resources'), - HGCAPP_DIR, - HGCAPP_SERVICES_HEDERA_PATH: `${HGCAPP_DIR}/services-hedera`, - HAPI_PATH: `${HGCAPP_DIR}/services-hedera/HapiApp2.0`, - ROOT_CONTAINER: 'root-container', - DATA_APPS_DIR: 'data/apps', - DATA_LIB_DIR: 'data/lib', - HEDERA_USER_HOME_DIR: '/home/hedera', - HEDERA_APP_JAR: 'HederaNode.jar', - HEDERA_NODE_DEFAULT_STAKE_AMOUNT: 1, - HEDERA_BUILDS_URL: 'https://builds.hedera.com', - LOG_STATUS_PROGRESS: chalk.cyan('>>'), - LOG_STATUS_DONE: chalk.green('OK'), - LOG_GROUP_DIVIDER: chalk.yellow('----------------------------------------------------------------------------'), - FST_CHART_REPO_URL: 'https://hashgraph.github.io/full-stack-testing/charts', - FST_CHART_SETUP_NAME: 'fullstack-cluster-setup', - FST_CHART_DEPLOYMENT_NAME: 'fullstack-deployment' -} +export const ROOT_CONTAINER = 'root-container' + +// --------------- Hedera related constants -------------------------------------------------------------------- +export const HEDERA_CHAIN_ID = '298' +export const HEDERA_HGCAPP_DIR = '/opt/hgcapp' +export const HEDERA_SERVICES_PATH = `${HEDERA_HGCAPP_DIR}/services-hedera` +export const HEDERA_HAPI_PATH = `${HEDERA_SERVICES_PATH}/HapiApp2.0` +export const HEDERA_DATA_APPS_DIR = 'data/apps' +export const HEDERA_DATA_LIB_DIR = 'data/lib' +export const HEDERA_USER_HOME_DIR = '/home/hedera' +export const HEDERA_APP_JAR = 'HederaNode.jar' +export const HEDERA_NODE_DEFAULT_STAKE_AMOUNT = 1 +export const HEDERA_BUILDS_URL = 'https://builds.hedera.com' + +// --------------- Logging related constants --------------------------------------------------------------------------- +export const LOG_STATUS_PROGRESS = chalk.cyan('>>') +export const LOG_STATUS_DONE = chalk.green('OK') +export const LOG_GROUP_DIVIDER = chalk.yellow('----------------------------------------------------------------------------') + +// --------------- Charts related constants ---------------------------------------------------------------------------- +export const CHART_REPO_FST_URL = 'https://hashgraph.github.io/full-stack-testing/charts' +export const CHART_FST_REPO_NAME = 'full-stack-testing' +export const CHART_FST_SETUP_NAME = 'fullstack-cluster-setup' +export const CHART_FST_DEPLOYMENT_NAME = 'fullstack-deployment' +export const CHART_REPO_JSON_RPC_RELAY_URL = 'https://hashgraph.github.io/hedera-json-rpc-relay/charts' +export const CHART_JSON_RPC_RELAY_REPO_NAME = 'hedera-json-rpc-relay' +export const CHART_JSON_RPC_RELAY_NAME = 'hedera-json-rpc-relay' +export const CHART_MIRROR_NODE_URL = 'https://hashgraph.github.io/hedera-mirror-node/charts' +export const CHART_MIRROR_NODE_REPO_NAME = 'hedera-mirror' +export const CHART_MIRROR_NODE_NAME = 'hedera-mirror' +export const DEFAULT_CHART_REPO = new Map() + .set(CHART_FST_REPO_NAME, CHART_REPO_FST_URL) + .set(CHART_JSON_RPC_RELAY_REPO_NAME, CHART_REPO_JSON_RPC_RELAY_URL) + .set(CHART_MIRROR_NODE_REPO_NAME, CHART_MIRROR_NODE_URL) diff --git a/fullstack-network-manager/src/core/index.mjs b/fullstack-network-manager/src/core/index.mjs index b2e4e8f62..a046f995e 100644 --- a/fullstack-network-manager/src/core/index.mjs +++ b/fullstack-network-manager/src/core/index.mjs @@ -1,5 +1,5 @@ import * as logging from './logging.mjs' -import { constants } from './constants.mjs' +import * as constants from './constants.mjs' import { Kind } from './kind.mjs' import { Helm } from './helm.mjs' import { Kubectl } from './kubectl.mjs' diff --git a/fullstack-network-manager/src/core/logging.mjs b/fullstack-network-manager/src/core/logging.mjs index 194994b3b..7a31fd69c 100644 --- a/fullstack-network-manager/src/core/logging.mjs +++ b/fullstack-network-manager/src/core/logging.mjs @@ -1,5 +1,5 @@ import * as winston from 'winston' -import { constants } from './constants.mjs' +import { constants } from './index.mjs' import { v4 as uuidv4 } from 'uuid' import * as util from 'util' import chalk from 'chalk' diff --git a/fullstack-network-manager/src/core/package_downloader.mjs b/fullstack-network-manager/src/core/package_downloader.mjs index 7eb966f48..c31a9ab63 100644 --- a/fullstack-network-manager/src/core/package_downloader.mjs +++ b/fullstack-network-manager/src/core/package_downloader.mjs @@ -1,3 +1,4 @@ +import chalk from 'chalk' import * as crypto from 'crypto' import * as fs from 'fs' import { pipeline as streamPipeline } from 'node:stream/promises' @@ -5,7 +6,7 @@ import got from 'got' import { DataValidationError, FullstackTestingError, IllegalArgumentError, ResourceNotFoundError } from './errors.mjs' import * as https from 'https' import { Templates } from './templates.mjs' -import { constants } from './constants.mjs' +import { constants } from './index.mjs' export class PackageDownloader { /** @@ -85,7 +86,7 @@ export class PackageDownloader { } if (!this.isValidURL(url)) { - throw new IllegalArgumentError('source URL is invalid', url) + throw new IllegalArgumentError(`source URL '${url}' is invalid`, url) } if (!await this.urlExists(url)) { @@ -175,6 +176,7 @@ export class PackageDownloader { const packageFile = `${downloadDir}/build-${tag}.zip` const checksumURL = `${constants.HEDERA_BUILDS_URL}/node/software/${releaseDir}/build-${tag}.sha384` const checksumPath = `${downloadDir}/build-${tag}.sha384` + this.logger.showUser(chalk.cyan('>>'), `Package URL: ${packageURL}`) try { if (fs.existsSync(packageFile) && !force) { diff --git a/fullstack-network-manager/src/core/platform_installer.mjs b/fullstack-network-manager/src/core/platform_installer.mjs index 1548341ce..ead0f9fe5 100644 --- a/fullstack-network-manager/src/core/platform_installer.mjs +++ b/fullstack-network-manager/src/core/platform_installer.mjs @@ -1,7 +1,7 @@ import { FullstackTestingError, IllegalArgumentError, MissingArgumentError } from './errors.mjs' import chalk from 'chalk' import * as fs from 'fs' -import { constants } from './constants.mjs' +import { constants } from './index.mjs' import { Templates } from './templates.mjs' import * as path from 'path' @@ -22,18 +22,18 @@ export class PlatformInstaller { try { // reset HAPI_PATH - await this.kubectl.execContainer(podName, containerName, `rm -rf ${constants.HGCAPP_SERVICES_HEDERA_PATH}`) + await this.kubectl.execContainer(podName, containerName, `rm -rf ${constants.HEDERA_SERVICES_PATH}`) const paths = [ - `${constants.HAPI_PATH}/data/keys`, - `${constants.HAPI_PATH}/data/config` + `${constants.HEDERA_HAPI_PATH}/data/keys`, + `${constants.HEDERA_HAPI_PATH}/data/config` ] for (const p of paths) { await this.kubectl.execContainer(podName, containerName, `mkdir -p ${p}`) } - await this.setPathPermission(podName, constants.HGCAPP_SERVICES_HEDERA_PATH) + await this.setPathPermission(podName, constants.HEDERA_SERVICES_PATH) return true } catch (e) { @@ -48,27 +48,27 @@ export class PlatformInstaller { } const dataDir = `${releaseDir}/data` - const appsDir = `${releaseDir}/${constants.DATA_APPS_DIR}` - const libDir = `${releaseDir}/${constants.DATA_LIB_DIR}` + const appsDir = `${releaseDir}/${constants.HEDERA_DATA_APPS_DIR}` + const libDir = `${releaseDir}/${constants.HEDERA_DATA_LIB_DIR}` if (!fs.existsSync(dataDir)) { throw new IllegalArgumentError('releaseDir does not have data directory', releaseDir) } if (!fs.existsSync(appsDir)) { - throw new IllegalArgumentError(`'${constants.DATA_APPS_DIR}' missing in '${releaseDir}'`, releaseDir) + throw new IllegalArgumentError(`'${constants.HEDERA_DATA_APPS_DIR}' missing in '${releaseDir}'`, releaseDir) } if (!fs.existsSync(libDir)) { - throw new IllegalArgumentError(`'${constants.DATA_LIB_DIR}' missing in '${releaseDir}'`, releaseDir) + throw new IllegalArgumentError(`'${constants.HEDERA_DATA_LIB_DIR}' missing in '${releaseDir}'`, releaseDir) } - if (!fs.statSync(`${releaseDir}/data/apps`).isEmpty()) { - throw new IllegalArgumentError(`'${constants.DATA_APPS_DIR}' is empty in releaseDir: ${releaseDir}`, releaseDir) + if (!fs.statSync(appsDir).isEmpty()) { + throw new IllegalArgumentError(`'${constants.HEDERA_DATA_APPS_DIR}' is empty in releaseDir: ${releaseDir}`, releaseDir) } - if (!fs.statSync(`${releaseDir}/data/lib`).isEmpty()) { - throw new IllegalArgumentError(`'${constants.DATA_LIB_DIR}' is empty in releaseDir: ${releaseDir}`, releaseDir) + if (!fs.statSync(libDir).isEmpty()) { + throw new IllegalArgumentError(`'${constants.HEDERA_DATA_LIB_DIR}' is empty in releaseDir: ${releaseDir}`, releaseDir) } } @@ -86,7 +86,7 @@ export class PlatformInstaller { await this.setupHapiDirectories(podName) await this.kubectl.execContainer(podName, constants.ROOT_CONTAINER, - `cd ${constants.HAPI_PATH} && jar xvf /home/hedera/build-*`) + `cd ${constants.HEDERA_HAPI_PATH} && jar xvf /home/hedera/build-*`) return true } catch (e) { @@ -125,7 +125,7 @@ export class PlatformInstaller { if (!stagingDir) throw new MissingArgumentError('stagingDir is required') try { - const keysDir = `${constants.HAPI_PATH}/data/keys` + const keysDir = `${constants.HEDERA_HAPI_PATH}/data/keys` const nodeId = Templates.extractNodeIdFromPodName(podName) const srcFiles = [ `${stagingDir}/templates/node-keys/private-${nodeId}.pfx`, @@ -151,7 +151,7 @@ export class PlatformInstaller { `${stagingDir}/templates/settings.txt` ] - const fileList1 = await self.copyFiles(podName, srcFilesSet1, constants.HAPI_PATH) + const fileList1 = await self.copyFiles(podName, srcFilesSet1, constants.HEDERA_HAPI_PATH) const srcFilesSet2 = [ `${stagingDir}/templates/properties/api-permission.properties`, @@ -159,7 +159,7 @@ export class PlatformInstaller { `${stagingDir}/templates/properties/bootstrap.properties` ] - const fileList2 = await self.copyFiles(podName, srcFilesSet2, `${constants.HAPI_PATH}/data/config`) + const fileList2 = await self.copyFiles(podName, srcFilesSet2, `${constants.HEDERA_HAPI_PATH}/data/config`) return fileList1.concat(fileList2) } catch (e) { @@ -174,7 +174,7 @@ export class PlatformInstaller { if (!stagingDir) throw new MissingArgumentError('stagingDir is required') try { - const destDir = constants.HAPI_PATH + const destDir = constants.HEDERA_HAPI_PATH const srcFiles = [ `${stagingDir}/templates/hedera.key`, `${stagingDir}/templates/hedera.crt` @@ -206,7 +206,7 @@ export class PlatformInstaller { try { const destPaths = [ - constants.HAPI_PATH + constants.HEDERA_HAPI_PATH ] for (const destPath of destPaths) { @@ -225,9 +225,10 @@ export class PlatformInstaller { * @param destPath path where config.txt should be written * @param releaseTag release tag e.g. v0.42.0 * @param template path to the confit.template file + * @param chainId chain ID (298 for local network) * @returns {Promise} */ - async prepareConfigTxt (nodeIDs, destPath, releaseTag, template = `${constants.RESOURCES_DIR}/templates/config.template`) { + async prepareConfigTxt (nodeIDs, destPath, releaseTag, chainId = constants.HEDERA_CHAIN_ID, template = `${constants.RESOURCES_DIR}/templates/config.template`) { const self = this if (!nodeIDs || nodeIDs.length === 0) throw new MissingArgumentError('list of node IDs is required') @@ -242,7 +243,7 @@ export class PlatformInstaller { const accountIdStart = process.env.FST_NODE_ACCOUNT_ID_START || '3' const internalPort = process.env.FST_NODE_INTERNAL_GOSSIP_PORT || '50111' const externalPort = process.env.FST_NODE_EXTERNAL_GOSSIP_PORT || '50111' - const ledgerName = process.env.FST_LEDGER_NAME || constants.CLUSTER_NAME + const ledgerId = process.env.FST_CHAIN_ID || chainId const appName = process.env.FST_HEDERA_APP_NAME || constants.HEDERA_APP_JAR const nodeStakeAmount = process.env.FST_NODE_DEFAULT_STAKE_AMOUNT || constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT @@ -252,7 +253,7 @@ export class PlatformInstaller { try { const configLines = [] - configLines.push(`swirld, ${ledgerName}`) + configLines.push(`swirld, ${ledgerId}`) configLines.push(`app, ${appName}`) let nodeSeq = 0 @@ -290,7 +291,7 @@ export class PlatformInstaller { } } - async prepareStaging (nodeIDs, stagingDir, releaseTag, force = false) { + async prepareStaging (nodeIDs, stagingDir, releaseTag, force = false, chainId = constants.HEDERA_CHAIN_ID) { const self = this try { if (!fs.existsSync(stagingDir)) { @@ -303,7 +304,7 @@ export class PlatformInstaller { fs.cpSync(`${constants.RESOURCES_DIR}/templates/`, `${stagingDir}/templates`, { recursive: true }) // prepare address book - await this.prepareConfigTxt(nodeIDs, configTxtPath, releaseTag, `${stagingDir}/templates/config.template`) + await this.prepareConfigTxt(nodeIDs, configTxtPath, releaseTag, chainId, `${stagingDir}/templates/config.template`) self.logger.showUser(chalk.green('OK'), `Prepared config.txt: ${configTxtPath}`) return true diff --git a/fullstack-network-manager/src/core/templates.mjs b/fullstack-network-manager/src/core/templates.mjs index 17ce0f288..0032e9a27 100644 --- a/fullstack-network-manager/src/core/templates.mjs +++ b/fullstack-network-manager/src/core/templates.mjs @@ -1,4 +1,4 @@ -import { DataValidationError } from './errors.mjs' +import { DataValidationError, MissingArgumentError } from './errors.mjs' export class Templates { static renderNetworkPodName (nodeId) { @@ -16,6 +16,8 @@ export class Templates { } static prepareReleasePrefix (tag) { + if (!tag) throw new MissingArgumentError('tag cannot be empty') + const parsed = tag.split('.') if (parsed.length < 3) throw new Error(`tag (${tag}) must include major, minor and patch fields (e.g. v0.40.4)`) return `${parsed[0]}.${parsed[1]}` diff --git a/fullstack-network-manager/src/index.mjs b/fullstack-network-manager/src/index.mjs index 3565dd7fb..ff7d86d4b 100644 --- a/fullstack-network-manager/src/index.mjs +++ b/fullstack-network-manager/src/index.mjs @@ -3,6 +3,7 @@ import { hideBin } from 'yargs/helpers' import * as commands from './commands/index.mjs' import * as core from './core/index.mjs' import { ChartManager, ConfigManager } from './core/index.mjs' +import 'dotenv/config' export function main (argv) { const logger = core.logging.NewLogger('debug') diff --git a/fullstack-network-manager/test/e2e/core/platform_installer_e2e.test.mjs b/fullstack-network-manager/test/e2e/core/platform_installer_e2e.test.mjs index 081cbbb31..6f8bfae9a 100644 --- a/fullstack-network-manager/test/e2e/core/platform_installer_e2e.test.mjs +++ b/fullstack-network-manager/test/e2e/core/platform_installer_e2e.test.mjs @@ -48,7 +48,7 @@ describe('PackageInstallerE2E', () => { try { packageFile = await downloader.fetchPlatform(packageTag, testCacheDir) await expect(installer.copyPlatform(podName, packageFile, true)).resolves.toBeTruthy() - const outputs = await kubectl.execContainer(podName, constants.ROOT_CONTAINER, `ls -la ${constants.HAPI_PATH}`) + const outputs = await kubectl.execContainer(podName, constants.ROOT_CONTAINER, `ls -la ${constants.HEDERA_HAPI_PATH}`) testLogger.showUser(outputs) } catch (e) { console.error(e) @@ -63,12 +63,13 @@ describe('PackageInstallerE2E', () => { const configPath = `${tmpDir}/config.txt` const nodeIDs = ['node0', 'node1', 'node2'] const releaseTag = 'v0.42.0' + const chainId = '299' - const configLines = await installer.prepareConfigTxt(nodeIDs, configPath, releaseTag) + const configLines = await installer.prepareConfigTxt(nodeIDs, configPath, releaseTag, chainId) // verify format is correct expect(configLines.length).toBe(6) - expect(configLines[0]).toBe(`swirld, ${constants.CLUSTER_NAME}`) + expect(configLines[0]).toBe(`swirld, ${chainId}`) expect(configLines[1]).toBe(`app, ${constants.HEDERA_APP_JAR}`) expect(configLines[2]).toContain('address, 0, node0, node0, 1') expect(configLines[3]).toContain('address, 1, node1, node1, 1') @@ -113,8 +114,8 @@ describe('PackageInstallerE2E', () => { const fileList = await installer.copyGossipKeys(podName, stagingDir) expect(fileList.length).toBe(2) - expect(fileList).toContain(`${constants.HAPI_PATH}/data/keys/private-node0.pfx`) - expect(fileList).toContain(`${constants.HAPI_PATH}/data/keys/public.pfx`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/data/keys/private-node0.pfx`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/data/keys/public.pfx`) }) it('should succeed to copy gossip keys for node1', async () => { @@ -124,8 +125,8 @@ describe('PackageInstallerE2E', () => { const fileList = await installer.copyGossipKeys(podName, stagingDir) expect(fileList.length).toBe(2) - expect(fileList).toContain(`${constants.HAPI_PATH}/data/keys/private-node1.pfx`) - expect(fileList).toContain(`${constants.HAPI_PATH}/data/keys/public.pfx`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/data/keys/private-node1.pfx`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/data/keys/public.pfx`) }) }) @@ -138,8 +139,8 @@ describe('PackageInstallerE2E', () => { const fileList = await installer.copyTLSKeys(podName, stagingDir) expect(fileList.length).toBe(3) // [data , hedera.crt, hedera.key] expect(fileList.length).toBeGreaterThanOrEqual(2) - expect(fileList).toContain(`${constants.HAPI_PATH}/hedera.crt`) - expect(fileList).toContain(`${constants.HAPI_PATH}/hedera.key`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/hedera.crt`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/hedera.key`) }) it('should succeed to copy TLS keys for node1', async () => { @@ -149,8 +150,8 @@ describe('PackageInstallerE2E', () => { const fileList = await installer.copyTLSKeys(podName, stagingDir) expect(fileList.length).toBe(3) // [data , hedera.crt, hedera.key] - expect(fileList).toContain(`${constants.HAPI_PATH}/hedera.crt`) - expect(fileList).toContain(`${constants.HAPI_PATH}/hedera.key`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/hedera.crt`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/hedera.key`) }) }) @@ -166,12 +167,12 @@ describe('PackageInstallerE2E', () => { const fileList = await installer.copyPlatformConfigFiles(podName, tmpDir) expect(fileList.length).toBeGreaterThanOrEqual(6) - expect(fileList).toContain(`${constants.HAPI_PATH}/config.txt`) - expect(fileList).toContain(`${constants.HAPI_PATH}/log4j2.xml`) - expect(fileList).toContain(`${constants.HAPI_PATH}/settings.txt`) - expect(fileList).toContain(`${constants.HAPI_PATH}/data/config/api-permission.properties`) - expect(fileList).toContain(`${constants.HAPI_PATH}/data/config/application.properties`) - expect(fileList).toContain(`${constants.HAPI_PATH}/data/config/bootstrap.properties`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/config.txt`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/log4j2.xml`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/settings.txt`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/data/config/api-permission.properties`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/data/config/application.properties`) + expect(fileList).toContain(`${constants.HEDERA_HAPI_PATH}/data/config/bootstrap.properties`) fs.rmSync(tmpDir, { recursive: true }) }, 10000) }) diff --git a/fullstack-network-manager/test/unit/core/package_downloader.test.mjs b/fullstack-network-manager/test/unit/core/package_downloader.test.mjs index dacaac275..099f2489f 100644 --- a/fullstack-network-manager/test/unit/core/package_downloader.test.mjs +++ b/fullstack-network-manager/test/unit/core/package_downloader.test.mjs @@ -3,7 +3,7 @@ import * as core from '../../../src/core/index.mjs' import * as fs from 'fs' import * as path from 'path' import * as os from 'os' -import { IllegalArgumentError, ResourceNotFoundError } from '../../../src/core/errors.mjs' +import { IllegalArgumentError, MissingArgumentError, ResourceNotFoundError } from '../../../src/core/errors.mjs' describe('PackageDownloader', () => { const testLogger = core.logging.NewLogger('debug') @@ -50,7 +50,7 @@ describe('PackageDownloader', () => { await downloader.fetchFile('INVALID_URL', os.tmpdir()) } catch (e) { expect(e).toBeInstanceOf(IllegalArgumentError) - expect(e.message).toBe('source URL is invalid') + expect(e.message).toBe("source URL 'INVALID_URL' is invalid") } }) @@ -89,6 +89,18 @@ describe('PackageDownloader', () => { it('should fail if platform release tag is missing', async () => { expect.assertions(2) + try { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'downloader-')) + await downloader.fetchPlatform('', tmpDir) + fs.rmSync(tmpDir, { recursive: true }) + } catch (e) { + expect(e.cause).not.toBeNull() + expect(e).toBeInstanceOf(MissingArgumentError) + } + }) + it('should fail if platform release artifact is not found', async () => { + expect.assertions(2) + const tag = 'v0.40.0-INVALID' try { diff --git a/fullstack-network-manager/test/unit/core/platform_installer.test.mjs b/fullstack-network-manager/test/unit/core/platform_installer.test.mjs index 6302f2a2b..e0ec7c4ce 100644 --- a/fullstack-network-manager/test/unit/core/platform_installer.test.mjs +++ b/fullstack-network-manager/test/unit/core/platform_installer.test.mjs @@ -28,7 +28,7 @@ describe('PackageInstaller', () => { expect.assertions(1) const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'installer-')) - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_LIB_DIR}`, { recursive: true }) + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_LIB_DIR}`, { recursive: true }) await expect(installer.validatePlatformReleaseDir(tmpDir)).rejects.toThrow(IllegalArgumentError) fs.rmSync(tmpDir, { recursive: true }) }) @@ -37,7 +37,7 @@ describe('PackageInstaller', () => { expect.assertions(1) const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'installer-')) - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_APPS_DIR}`, { recursive: true }) + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_APPS_DIR}`, { recursive: true }) await expect(installer.validatePlatformReleaseDir(tmpDir)).rejects.toThrow(IllegalArgumentError) fs.rmSync(tmpDir, { recursive: true }) }) @@ -46,28 +46,28 @@ describe('PackageInstaller', () => { expect.assertions(1) const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'installer-')) - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_APPS_DIR}`, { recursive: true }) - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_LIB_DIR}`, { recursive: true }) - fs.writeFileSync(`${tmpDir}/${core.constants.DATA_LIB_DIR}/test.jar`, '') + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_APPS_DIR}`, { recursive: true }) + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_LIB_DIR}`, { recursive: true }) + fs.writeFileSync(`${tmpDir}/${core.constants.HEDERA_DATA_LIB_DIR}/test.jar`, '') await expect(installer.validatePlatformReleaseDir()).rejects.toThrow(MissingArgumentError) fs.rmSync(tmpDir, { recursive: true }) }) it('should fail if directory does not have data/apps directory is empty', async () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'installer-app-')) - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_APPS_DIR}`, { recursive: true }) - fs.writeFileSync(`${tmpDir}/${core.constants.DATA_APPS_DIR}/app.jar`, '') - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_LIB_DIR}`, { recursive: true }) + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_APPS_DIR}`, { recursive: true }) + fs.writeFileSync(`${tmpDir}/${core.constants.HEDERA_DATA_APPS_DIR}/app.jar`, '') + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_LIB_DIR}`, { recursive: true }) await expect(installer.validatePlatformReleaseDir()).rejects.toThrow(MissingArgumentError) fs.rmSync(tmpDir, { recursive: true }) }) it('should succeed with non-empty data/apps and data/libs directory', async () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'installer-lib-')) - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_APPS_DIR}`, { recursive: true }) - fs.writeFileSync(`${tmpDir}/${core.constants.DATA_APPS_DIR}/app.jar`, '') - fs.mkdirSync(`${tmpDir}/${core.constants.DATA_LIB_DIR}`, { recursive: true }) - fs.writeFileSync(`${tmpDir}/${core.constants.DATA_LIB_DIR}/lib-1.jar`, '') + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_APPS_DIR}`, { recursive: true }) + fs.writeFileSync(`${tmpDir}/${core.constants.HEDERA_DATA_APPS_DIR}/app.jar`, '') + fs.mkdirSync(`${tmpDir}/${core.constants.HEDERA_DATA_LIB_DIR}`, { recursive: true }) + fs.writeFileSync(`${tmpDir}/${core.constants.HEDERA_DATA_LIB_DIR}/lib-1.jar`, '') await expect(installer.validatePlatformReleaseDir()).rejects.toThrow(MissingArgumentError) fs.rmSync(tmpDir, { recursive: true }) })