From 74c9e2cd7b17fd983dbd7107dc8fd7adf1dbf794 Mon Sep 17 00:00:00 2001 From: Jan Milenkov Date: Wed, 4 Sep 2024 15:49:08 +0300 Subject: [PATCH] fix: improve types in JSDoc and fix typo in '/src/commands/account.mjs' (#507) Signed-off-by: instamenta Signed-off-by: Jan Milenkov Co-authored-by: Jeromy Cannon --- src/commands/account.mjs | 52 +++- src/commands/base.mjs | 13 + src/commands/cluster.mjs | 25 +- src/commands/flags.mjs | 1 + src/commands/index.mjs | 6 +- src/commands/init.mjs | 8 +- src/commands/mirror_node.mjs | 27 +- src/commands/network.mjs | 44 +++- src/commands/node.mjs | 138 +++++++++- src/commands/prompts.mjs | 6 +- src/commands/relay.mjs | 48 +++- src/core/account_manager.mjs | 100 ++++---- src/core/chart_manager.mjs | 36 ++- src/core/config_manager.mjs | 33 ++- src/core/constants.mjs | 9 +- .../dependency_manager.mjs | 13 +- .../helm_dependency_manager.mjs | 31 ++- .../keytool_dependency_manager.mjs | 36 ++- src/core/errors.mjs | 32 +-- src/core/helm.mjs | 28 ++- src/core/helpers.mjs | 93 ++++++- src/core/k8.mjs | 235 ++++++++++-------- src/core/key_manager.mjs | 150 ++++++----- src/core/keytool.mjs | 21 +- src/core/logging.mjs | 49 ++++ src/core/network_node_services.mjs | 112 +++++++++ src/core/package_downloader.mjs | 46 ++-- src/core/platform_installer.mjs | 45 +++- src/core/profile_manager.mjs | 97 ++++++-- src/core/shell_runner.mjs | 8 +- src/core/templates.mjs | 95 ++++++- src/core/zippy.mjs | 26 +- 32 files changed, 1296 insertions(+), 367 deletions(-) diff --git a/src/commands/account.mjs b/src/commands/account.mjs index 00c68001a..711e45bb2 100644 --- a/src/commands/account.mjs +++ b/src/commands/account.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import chalk from 'chalk' import { BaseCommand } from './base.mjs' import { FullstackTestingError, IllegalArgumentError } from '../core/errors.mjs' @@ -25,6 +26,10 @@ import { AccountInfo, HbarUnit, PrivateKey } from '@hashgraph/sdk' import { FREEZE_ADMIN_ACCOUNT } from '../core/constants.mjs' export class AccountCommand extends BaseCommand { + /** + * @param {{accountManager: AccountManager, logger: Logger, helm: Helm, k8: K8, chartManager: ChartManager, configManager: ConfigManager, depManager: DependencyManager}} opts + * @param {number[][]} [systemAccounts] + */ constructor (opts, systemAccounts = constants.SYSTEM_ACCOUNTS) { super(opts) @@ -35,10 +40,19 @@ export class AccountCommand extends BaseCommand { this.systemAccounts = systemAccounts } + /** + * @returns {Promise} + */ async closeConnections () { await this.accountManager.close() } + /** + * @param {AccountInfo} accountInfo + * @param {string} namespace + * @param {boolean} shouldRetrievePrivateKey + * @returns {Promise<{accountId: string, balance: number, publicKey: string}>} + */ async buildAccountInfo (accountInfo, namespace, shouldRetrievePrivateKey) { if (!accountInfo || !(accountInfo instanceof AccountInfo)) throw new IllegalArgumentError('An instance of AccountInfo is required') @@ -56,6 +70,10 @@ export class AccountCommand extends BaseCommand { return newAccountInfo } + /** + * @param {any} ctx + * @returns {Promise<{accountId: AccountId, privateKey: string, publicKey: string, balance: number}>} + */ async createNewAccount (ctx) { if (ctx.config.ecdsaPrivateKey) { ctx.privateKey = PrivateKey.fromStringECDSA(ctx.config.ecdsaPrivateKey) @@ -69,10 +87,18 @@ export class AccountCommand extends BaseCommand { ctx.privateKey, ctx.config.amount, ctx.config.ecdsaPrivateKey ? ctx.config.setAlias : false) } + /** + * @param {any} ctx + * @returns {Promise} + */ async getAccountInfo (ctx) { return this.accountManager.accountInfoQuery(ctx.config.accountId) } + /** + * @param {any} ctx + * @returns {Promise} + */ async updateAccountInfo (ctx) { let amount = ctx.config.amount if (ctx.config.privateKey) { @@ -99,10 +125,19 @@ export class AccountCommand extends BaseCommand { return true } + /** + * @param {AccountId} toAccountId + * @param {number} amount + * @returns {Promise} + */ async transferAmountFromOperator (toAccountId, amount) { return await this.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, toAccountId, amount) } + /** + * @param {Object} argv + * @returns {Promise} + */ async init (argv) { const self = this @@ -220,6 +255,10 @@ export class AccountCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async create (argv) { const self = this @@ -282,6 +321,10 @@ export class AccountCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async update (argv) { const self = this @@ -351,6 +394,10 @@ export class AccountCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async get (argv) { const self = this @@ -406,10 +453,11 @@ export class AccountCommand extends BaseCommand { /** * Return Yargs command definition for 'node' command - * @param accountCmd an instance of NodeCommand + * @param {AccountCommand} accountCmd an instance of NodeCommand + * @returns {{command: string, desc: string, builder: Function}} */ static getCommandDefinition (accountCmd) { - if (!accountCmd | !(accountCmd instanceof AccountCommand)) { + if (!accountCmd || !(accountCmd instanceof AccountCommand)) { throw new IllegalArgumentError('An instance of AccountCommand is required', accountCmd) } return { diff --git a/src/commands/base.mjs b/src/commands/base.mjs index e6b41b199..94c768fa0 100644 --- a/src/commands/base.mjs +++ b/src/commands/base.mjs @@ -20,6 +20,12 @@ import { MissingArgumentError } from '../core/errors.mjs' import { ShellRunner } from '../core/shell_runner.mjs' export class BaseCommand extends ShellRunner { + /** + * @param {string} chartDir + * @param {string} chartRepo + * @param {string} chartReleaseName + * @returns {Promise} + */ async prepareChartPath (chartDir, chartRepo, chartReleaseName) { if (!chartRepo) throw new MissingArgumentError('chart repo name is required') if (!chartReleaseName) throw new MissingArgumentError('chart release name is required') @@ -33,6 +39,10 @@ export class BaseCommand extends ShellRunner { return `${chartRepo}/${chartReleaseName}` } + /** + * @param {string} valuesFile + * @returns {string} + */ prepareValuesFiles (valuesFile) { let valuesArg = '' if (valuesFile) { @@ -46,6 +56,9 @@ export class BaseCommand extends ShellRunner { return valuesArg } + /** + * @param {{logger: Logger, helm: Helm, k8: K8, chartManager: ChartManager, configManager: ConfigManager, depManager: DependencyManager}} opts + */ constructor (opts) { if (!opts || !opts.logger) throw new Error('An instance of core/Logger is required') if (!opts || !opts.helm) throw new Error('An instance of core/Helm is required') diff --git a/src/commands/cluster.mjs b/src/commands/cluster.mjs index 631dc360c..95540147e 100644 --- a/src/commands/cluster.mjs +++ b/src/commands/cluster.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { ListrEnquirerPromptAdapter } from '@listr2/prompt-adapter-enquirer' import { Listr } from 'listr2' import { FullstackTestingError, IllegalArgumentError } from '../core/errors.mjs' @@ -28,6 +29,9 @@ import path from 'path' * Define the core functionalities of 'cluster' command */ export class ClusterCommand extends BaseCommand { + /** + * @returns {Promise} + */ async showClusterList () { this.logger.showList('Clusters', await this.k8.getClusters()) return true @@ -52,7 +56,7 @@ export class ClusterCommand extends BaseCommand { /** * Show list of installed chart - * @param clusterSetupNamespace + * @param {string} clusterSetupNamespace */ async showInstalledChartList (clusterSetupNamespace) { this.logger.showList('Installed Charts', await this.chartManager.getInstalledCharts(clusterSetupNamespace)) @@ -60,7 +64,7 @@ export class ClusterCommand extends BaseCommand { /** * Setup cluster with shared components - * @param argv + * @param {Object} argv * @returns {Promise} */ async setup (argv) { @@ -155,7 +159,7 @@ export class ClusterCommand extends BaseCommand { /** * Uninstall shared components from the cluster and perform any other necessary cleanups - * @param argv + * @param {Object} argv * @returns {Promise} */ async reset (argv) { @@ -218,7 +222,8 @@ export class ClusterCommand extends BaseCommand { /** * Return Yargs command definition for 'cluster' command - * @param clusterCmd an instance of ClusterCommand + * @param {ClusterCommand} clusterCmd - an instance of ClusterCommand + * @returns {{command: string, desc: string, builder: Function}} */ static getCommandDefinition (clusterCmd) { if (!clusterCmd || !(clusterCmd instanceof ClusterCommand)) { @@ -315,11 +320,11 @@ export class ClusterCommand extends BaseCommand { /** * Prepare values arg for cluster setup command * - * @param chartDir local charts directory (default is empty) - * @param prometheusStackEnabled a bool to denote whether to install prometheus stack - * @param minioEnabled a bool to denote whether to install minio - * @param certManagerEnabled a bool to denote whether to install cert manager - * @param certManagerCrdsEnabled a bool to denote whether to install cert manager CRDs + * @param {string} chartDir - local charts directory (default is empty) + * @param {boolean} prometheusStackEnabled - a bool to denote whether to install prometheus stack + * @param {boolean} minioEnabled - a bool to denote whether to install minio + * @param {boolean} certManagerEnabled - a bool to denote whether to install cert manager + * @param {boolean} certManagerCrdsEnabled - a bool to denote whether to install cert manager CRDs * @returns {string} */ prepareValuesArg (chartDir = flags.chartDirectory.definition.default, @@ -348,7 +353,7 @@ export class ClusterCommand extends BaseCommand { /** * Prepare chart path - * @param chartDir local charts directory (default is empty) + * @param {string} [chartDir] - local charts directory (default is empty) * @returns {Promise} */ async prepareChartPath (chartDir = flags.chartDirectory.definition.default) { diff --git a/src/commands/flags.mjs b/src/commands/flags.mjs index f5d2670fc..a63e88d2a 100644 --- a/src/commands/flags.mjs +++ b/src/commands/flags.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { constants } from '../core/index.mjs' import * as core from '../core/index.mjs' import * as version from '../../version.mjs' diff --git a/src/commands/index.mjs b/src/commands/index.mjs index abb7942e1..255df9f9e 100644 --- a/src/commands/index.mjs +++ b/src/commands/index.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { ClusterCommand } from './cluster.mjs' import { InitCommand } from './init.mjs' import { MirrorNodeCommand } from './mirror_node.mjs' @@ -23,9 +24,10 @@ import { RelayCommand } from './relay.mjs' import { AccountCommand } from './account.mjs' import * as flags from './flags.mjs' -/* +/** * Return a list of Yargs command builder to be exposed through CLI - * @param opts it is an Options object containing logger + * @param {Object} opts it is an Options object containing logger + * @returns {Array<{command: string, desc: string, builder: Function, handler?: Function}[]>} */ function Initialize (opts) { const initCmd = new InitCommand(opts) diff --git a/src/commands/init.mjs b/src/commands/init.mjs index f46221487..72c678736 100644 --- a/src/commands/init.mjs +++ b/src/commands/init.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { Listr } from 'listr2' import path from 'path' import { BaseCommand } from './base.mjs' @@ -30,7 +31,8 @@ import chalk from 'chalk' export class InitCommand extends BaseCommand { /** * Setup home directories - * @param dirs a list of directories that need to be created in sequence + * @param {string[]} dirs a list of directories that need to be created in sequence + * @returns {string[]} */ setupHomeDirectory (dirs = [ constants.SOLO_HOME_DIR, @@ -57,6 +59,7 @@ export class InitCommand extends BaseCommand { /** * Executes the init CLI command + * @param {Object} argv * @returns {Promise} */ async init (argv) { @@ -141,7 +144,8 @@ export class InitCommand extends BaseCommand { /** * Return Yargs command definition for 'init' command - * @param initCmd an instance of InitCommand + * @param {InitCommand} initCmd - an instance of InitCommand + * @returns {{command: string, desc: string, builder: Function, handler: (argv: Object) => void}} */ static getCommandDefinition (initCmd) { if (!initCmd || !(initCmd instanceof InitCommand)) { diff --git a/src/commands/mirror_node.mjs b/src/commands/mirror_node.mjs index c47409b47..e33238daa 100644 --- a/src/commands/mirror_node.mjs +++ b/src/commands/mirror_node.mjs @@ -24,6 +24,11 @@ import * as prompts from './prompts.mjs' import { getFileContents, getEnvValue } from '../core/helpers.mjs' export class MirrorNodeCommand extends BaseCommand { + /** + * @param {{accountManager: AccountManager, profileManager: ProfileManager, logger: Logger, helm: Helm, k8: K8, + * hartManager: ChartManager, configManager: ConfigManager, depManager: DependencyManager, + * downloader: PackageDownloader}} opts + */ constructor (opts) { super(opts) if (!opts || !opts.accountManager) throw new IllegalArgumentError('An instance of core/AccountManager is required', opts.accountManager) @@ -33,10 +38,16 @@ export class MirrorNodeCommand extends BaseCommand { this.profileManager = opts.profileManager } + /** + * @returns {string} + */ static get DEPLOY_CONFIGS_NAME () { return 'deployConfigs' } + /** + * @returns {CommandFlag[]} + */ static get DEPLOY_FLAGS_LIST () { return [ flags.chartDirectory, @@ -49,6 +60,11 @@ export class MirrorNodeCommand extends BaseCommand { ] } + /** + * @param {string} valuesFile + * @param {boolean} deployHederaExplorer + * @returns {Promise} + */ async prepareValuesArg (valuesFile, deployHederaExplorer) { let valuesArg = '' if (valuesFile) { @@ -65,6 +81,10 @@ export class MirrorNodeCommand extends BaseCommand { return valuesArg } + /** + * @param {Object} argv + * @returns {Promise} + */ async deploy (argv) { const self = this @@ -275,6 +295,10 @@ export class MirrorNodeCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async destroy (argv) { const self = this @@ -364,7 +388,8 @@ export class MirrorNodeCommand extends BaseCommand { /** * Return Yargs command definition for 'mirror-mirror-node' command - * @param mirrorNodeCmd an instance of NodeCommand + * @param {MirrorNodeCommand} mirrorNodeCmd an instance of MirrorNodeCommand + * @returns {{command: string, desc: string, builder: Function}} */ static getCommandDefinition (mirrorNodeCmd) { if (!mirrorNodeCmd || !(mirrorNodeCmd instanceof MirrorNodeCommand)) { diff --git a/src/commands/network.mjs b/src/commands/network.mjs index e1eb458bc..ebdcb9f56 100644 --- a/src/commands/network.mjs +++ b/src/commands/network.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { ListrEnquirerPromptAdapter } from '@listr2/prompt-adapter-enquirer' import chalk from 'chalk' import { Listr } from 'listr2' @@ -28,6 +29,10 @@ import { validatePath } from '../core/helpers.mjs' import fs from 'fs' export class NetworkCommand extends BaseCommand { + /** + * @param {{profileManager: ProfileManager, logger: Logger, helm: Helm, k8: K8, chartManager: ChartManager, + * configManager: ConfigManager, depManager: DependencyManager, downloader: PackageDownloader}} opts + */ constructor (opts) { super(opts) @@ -42,10 +47,16 @@ export class NetworkCommand extends BaseCommand { this.profileManager = opts.profileManager } + /** + * @returns {string} + */ static get DEPLOY_CONFIGS_NAME () { return 'deployConfigs' } + /** + * @returns {CommandFlag[]} + */ static get DEPLOY_FLAGS_LIST () { return [ flags.apiPermissionProperties, @@ -77,6 +88,14 @@ export class NetworkCommand extends BaseCommand { ] } + /** + * @param {string} tlsClusterIssuerType + * @param {boolean} enableHederaExplorerTls + * @param {string} namespace + * @param {string} hederaExplorerTlsLoadBalancerIp + * @param {string} hederaExplorerTlsHostName + * @returns {string} + */ getTlsValueArguments (tlsClusterIssuerType, enableHederaExplorerTls, namespace, hederaExplorerTlsLoadBalancerIp, hederaExplorerTlsHostName) { let valuesArg = '' @@ -106,6 +125,10 @@ export class NetworkCommand extends BaseCommand { return valuesArg } + /** + * @param {Object} config + * @returns {Promise} + */ async prepareValuesArg (config = {}) { let valuesArg = '' if (config.chartDirectory) { @@ -142,6 +165,11 @@ export class NetworkCommand extends BaseCommand { return valuesArg } + /** + * @param task + * @param {Object} argv + * @returns {Promise} + */ async prepareConfig (task, argv) { this.configManager.update(argv) this.logger.debug('Loaded cached config', { config: this.configManager.config }) @@ -254,8 +282,8 @@ export class NetworkCommand extends BaseCommand { /** * Run helm install and deploy network components - * @param argv - * @return {Promise} + * @param {Object} argv + * @returns {Promise} */ async deploy (argv) { const self = this @@ -433,8 +461,8 @@ export class NetworkCommand extends BaseCommand { /** * Run helm uninstall and destroy network components - * @param argv - * @return {Promise} + * @param {Object} argv + * @returns {Promise} */ async destroy (argv) { const self = this @@ -517,8 +545,8 @@ export class NetworkCommand extends BaseCommand { /** * Run helm upgrade to refresh network components with new settings - * @param argv - * @return {Promise} + * @param {Object} argv + * @returns {Promise} */ async refresh (argv) { const self = this @@ -565,6 +593,10 @@ export class NetworkCommand extends BaseCommand { return true } + /** + * @param {NetworkCommand} networkCmd + * @returns {{command: string, desc: string, builder: Function}} + */ static getCommandDefinition (networkCmd) { if (!networkCmd || !(networkCmd instanceof NetworkCommand)) { throw new IllegalArgumentError('An instance of NetworkCommand is required', networkCmd) diff --git a/src/commands/node.mjs b/src/commands/node.mjs index 510571e77..ebef82c4a 100644 --- a/src/commands/node.mjs +++ b/src/commands/node.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import * as x509 from '@peculiar/x509' import chalk from 'chalk' import * as fs from 'fs' @@ -59,6 +60,12 @@ import { * Defines the core functionalities of 'node' command */ export class NodeCommand extends BaseCommand { + /** + * @param {{logger: Logger, helm: Helm, k8: K8, chartManager: ChartManager, configManager: ConfigManager, + * depManager: DependencyManager, keytoolDepManager: KeytoolDependencyManager, downloader: PackageDownloader, + * platformInstaller: PlatformInstaller, keyManager: KeyManager, accountManager: AccountManager, + * profileManager: ProfileManager}} opts + */ constructor (opts) { super(opts) @@ -78,10 +85,16 @@ export class NodeCommand extends BaseCommand { this._portForwards = [] } + /** + * @returns {string} + */ static get SETUP_CONFIGS_NAME () { return 'setupConfigs' } + /** + * @returns {CommandFlag[]} + */ static get SETUP_FLAGS_LIST () { return [ flags.appConfig, @@ -94,10 +107,16 @@ export class NodeCommand extends BaseCommand { ] } + /** + * @returns {string} + */ static get KEYS_CONFIGS_NAME () { return 'keysConfigs' } + /** + * @returns {CommandFlag[]} + */ static get KEYS_FLAGS_LIST () { return [ flags.cacheDir, @@ -109,10 +128,16 @@ export class NodeCommand extends BaseCommand { ] } + /** + * @returns {string} + */ static get REFRESH_CONFIGS_NAME () { return 'refreshConfigs' } + /** + * @returns {CommandFlag[]} + */ static get REFRESH_FLAGS_LIST () { return [ flags.app, @@ -125,10 +150,16 @@ export class NodeCommand extends BaseCommand { ] } + /** + * @returns {string} + */ static get ADD_CONFIGS_NAME () { return 'addConfigs' } + /** + * @returns {CommandFlag[]} + */ static get ADD_FLAGS_LIST () { return [ flags.app, @@ -214,6 +245,12 @@ export class NodeCommand extends BaseCommand { this._portForwards = [] } + /** + * @param {string} namespace + * @param {string} accountId + * @param {string} nodeId + * @returns {Promise} + */ async addStake (namespace, accountId, nodeId) { try { await this.accountManager.loadNodeClient(namespace) @@ -254,6 +291,13 @@ export class NodeCommand extends BaseCommand { } } + /** + * @param {string} namespace + * @param {string} nodeId + * @param {number} [maxAttempts] + * @param {number} [delay] + * @returns {Promise} + */ async checkNetworkNodePod (namespace, nodeId, maxAttempts = 60, delay = 2000) { nodeId = nodeId.trim() const podName = Templates.renderNetworkPodName(nodeId) @@ -270,6 +314,13 @@ export class NodeCommand extends BaseCommand { } } + /** + * @param {string} nodeId + * @param {number} [maxAttempt] + * @param {string} [status] + * @param {string} [logfile] + * @returns {Promise} + */ async checkNetworkNodeState (nodeId, maxAttempt = 100, status = 'ACTIVE', logfile = 'output/hgcaa.log') { nodeId = nodeId.trim() const podName = Templates.renderNetworkPodName(nodeId) @@ -334,6 +385,10 @@ export class NodeCommand extends BaseCommand { /** * Return task for checking for all network node pods + * @param {any} ctx + * @param {typeof import('listr2').TaskWrapper} task + * @param {string[]} nodeIds + * @returns {*} */ taskCheckNetworkNodePods (ctx, task, nodeIds) { if (!ctx.config) { @@ -385,6 +440,13 @@ export class NodeCommand extends BaseCommand { } } + /** + * @param {string[]} nodeIds + * @param {Object} podNames + * @param {typeof import('listr2').TaskWrapper} task + * @param {string} localBuildPath + * @returns {Listr<*, *, *>} + */ uploadPlatformSoftware (nodeIds, podNames, task, localBuildPath) { const self = this const subTasks = [] @@ -437,6 +499,14 @@ export class NodeCommand extends BaseCommand { }) } + /** + * @param {string[]} nodeIds + * @param {Object} podNames + * @param {string} releaseTag + * @param {typeof import('listr2').TaskWrapper} task + * @param {string} localBuildPath + * @returns {Listr<*, *, *>} + */ fetchLocalOrReleasedPlatformSoftware (nodeIds, podNames, releaseTag, task, localBuildPath) { const self = this if (localBuildPath !== '') { @@ -446,6 +516,14 @@ export class NodeCommand extends BaseCommand { } } + /** + * @param {string[]} nodeIds + * @param {Object} podNames + * @param {string} releaseTag + * @param {typeof import('listr2').TaskWrapper} task + * @param {PlatformInstaller} platformInstaller + * @returns {Listr} + */ fetchPlatformSoftware (nodeIds, podNames, releaseTag, task, platformInstaller) { const subTasks = [] for (const nodeId of nodeIds) { @@ -506,6 +584,11 @@ export class NodeCommand extends BaseCommand { return await zipper.zip(path.join(stagingDir, 'mock-upgrade'), path.join(stagingDir, 'mock-upgrade.zip')) } + /** + * @param {string} upgradeZipFile + * @param nodeClient + * @returns {Promise} + */ async uploadUpgradeZip (upgradeZipFile, nodeClient) { // get byte value of the zip file const zipBytes = fs.readFileSync(upgradeZipFile) @@ -542,6 +625,12 @@ export class NodeCommand extends BaseCommand { } } + /** + * @param {string} endpointType + * @param {string[]} endpoints + * @param {number} defaultPort + * @returns {ServiceEndpoint[]} + */ prepareEndpoints (endpointType, endpoints, defaultPort) { const ret = /** @typedef ServiceEndpoint **/[] for (const endpoint of endpoints) { @@ -576,7 +665,10 @@ export class NodeCommand extends BaseCommand { } // List of Commands - + /** + * @param {Object} argv + * @returns {Promise} + */ async setup (argv) { const self = this @@ -680,6 +772,10 @@ export class NodeCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async start (argv) { const self = this @@ -820,6 +916,10 @@ export class NodeCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async stop (argv) { const self = this @@ -883,6 +983,10 @@ export class NodeCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async keys (argv) { const self = this const tasks = new Listr([ @@ -990,6 +1094,10 @@ export class NodeCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async refresh (argv) { const self = this @@ -1184,6 +1292,10 @@ export class NodeCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async logs (argv) { const self = this @@ -1224,6 +1336,10 @@ export class NodeCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async add (argv) { const self = this @@ -1871,6 +1987,12 @@ export class NodeCommand extends BaseCommand { return true } + /** + * @param {PrivateKey|string} freezeAdminPrivateKey + * @param {Uint8Array|string} upgradeZipHash + * @param {Client} client + * @returns {Promise} + */ async prepareUpgradeNetworkNodes (freezeAdminPrivateKey, upgradeZipHash, client) { try { // transfer some tiny amount to the freeze admin account @@ -1904,6 +2026,12 @@ export class NodeCommand extends BaseCommand { } } + /** + * @param {PrivateKey|string} freezeAdminPrivateKey + * @param {Uint8Array|string} upgradeZipHash + * @param {Client} client + * @returns {Promise} + */ async freezeUpgradeNetworkNodes (freezeAdminPrivateKey, upgradeZipHash, client) { try { const futureDate = new Date() @@ -1930,6 +2058,11 @@ export class NodeCommand extends BaseCommand { } } + /** + * @param {Object} podNames + * @param {string} nodeIds + * @param {{title: string, task: () => Promise}[]} subTasks + */ startNodes (podNames, nodeIds, subTasks) { for (const nodeId of nodeIds) { const podName = podNames[nodeId] @@ -1945,7 +2078,8 @@ export class NodeCommand extends BaseCommand { // Command Definition /** * Return Yargs command definition for 'node' command - * @param nodeCmd an instance of NodeCommand + * @param {NodeCommand} nodeCmd - an instance of NodeCommand + * @returns {{command: string, desc: string, builder: Function}} */ static getCommandDefinition (nodeCmd) { if (!nodeCmd || !(nodeCmd instanceof NodeCommand)) { diff --git a/src/commands/prompts.mjs b/src/commands/prompts.mjs index 6a5b32ccc..33c8b4459 100644 --- a/src/commands/prompts.mjs +++ b/src/commands/prompts.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { ListrEnquirerPromptAdapter } from '@listr2/prompt-adapter-enquirer' import fs from 'fs' import { FullstackTestingError, IllegalArgumentError } from '../core/errors.mjs' @@ -453,6 +454,9 @@ export async function promptPersistentVolumeClaims (task, input) { flags.persistentVolumeClaims.name) } +/** + * @returns {Map} + */ export function getPromptMap () { return new Map() .set(flags.accountId.name, promptAccountId) @@ -503,7 +507,7 @@ export function getPromptMap () { * @param task task object from listr2 * @param configManager config manager to store flag values * @param {CommandFlag[]} flagList list of flag objects - * @return {Promise} + * @returns {Promise} */ export async function execute (task, configManager, flagList = []) { if (!configManager || !(configManager instanceof ConfigManager)) { diff --git a/src/commands/relay.mjs b/src/commands/relay.mjs index f19cd3673..b38e43a39 100644 --- a/src/commands/relay.mjs +++ b/src/commands/relay.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { Listr } from 'listr2' import { FullstackTestingError, MissingArgumentError } from '../core/errors.mjs' import * as helpers from '../core/helpers.mjs' @@ -24,6 +25,11 @@ import * as prompts from './prompts.mjs' import { getNodeAccountMap } from '../core/helpers.mjs' export class RelayCommand extends BaseCommand { + /** + * @param {{profileManager: ProfileManager, accountManager?: AccountManager, logger: Logger, helm: Helm, k8: K8, + * chartManager: ChartManager, configManager: ConfigManager, depManager: DependencyManager, + * downloader: PackageDownloader}} opts + */ constructor (opts) { super(opts) @@ -33,10 +39,16 @@ export class RelayCommand extends BaseCommand { this.accountManager = opts.accountManager } + /** + * @returns {string} + */ static get DEPLOY_CONFIGS_NAME () { return 'deployConfigs' } + /** + * @returns {CommandFlag[]} + */ static get DEPLOY_FLAGS_LIST () { return [ flags.chainId, @@ -53,6 +65,17 @@ export class RelayCommand extends BaseCommand { ] } + /** + * @param {string} valuesFile + * @param {string} nodeIDs + * @param {string} chainID + * @param {string} relayRelease + * @param {number} replicaCount + * @param {string} operatorID + * @param {string} operatorKey + * @param {string} namespace + * @returns {Promise} + */ async prepareValuesArg (valuesFile, nodeIDs, chainID, relayRelease, replicaCount, operatorID, operatorKey, namespace) { let valuesArg = '' if (valuesFile) { @@ -100,8 +123,13 @@ export class RelayCommand extends BaseCommand { return valuesArg } - // created a json string to represent the map between the node keys and their ids - // output example '{"node-1": "0.0.3", "node-2": "0.004"}' + /** + * created a json string to represent the map between the node keys and their ids + * output example '{"node-1": "0.0.3", "node-2": "0.004"}' + * @param {string[]} nodeIDs + * @param {string} namespace + * @returns {Promise} + */ async prepareNetworkJsonString (nodeIDs = [], namespace) { if (!nodeIDs) { throw new MissingArgumentError('Node IDs must be specified') @@ -121,6 +149,10 @@ export class RelayCommand extends BaseCommand { return JSON.stringify(networkIds) } + /** + * @param {string[]} nodeIDs + * @returns {string} + */ prepareReleaseName (nodeIDs = []) { if (!nodeIDs) { throw new MissingArgumentError('Node IDs must be specified') @@ -134,6 +166,10 @@ export class RelayCommand extends BaseCommand { return releaseName } + /** + * @param {Object} argv + * @returns {Promise} + */ async deploy (argv) { const self = this const tasks = new Listr([ @@ -248,6 +284,10 @@ export class RelayCommand extends BaseCommand { return true } + /** + * @param {Object} argv + * @returns {Promise} + */ async destroy (argv) { const self = this @@ -307,6 +347,10 @@ export class RelayCommand extends BaseCommand { return true } + /** + * @param {RelayCommand} relayCmd + * @returns {{command: string, desc: string, builder: Function}} + */ static getCommandDefinition (relayCmd) { if (!relayCmd || !(relayCmd instanceof RelayCommand)) { throw new MissingArgumentError('An instance of RelayCommand is required', relayCmd) diff --git a/src/core/account_manager.mjs b/src/core/account_manager.mjs index 848e36d28..c5228af30 100644 --- a/src/core/account_manager.mjs +++ b/src/core/account_manager.mjs @@ -40,6 +40,13 @@ import ip from 'ip' import { NetworkNodeServicesBuilder } from './network_node_services.mjs' import path from 'path' +/** + * @typedef {Object} AccountIdWithKeyPairObject + * @property {string} accountId + * @property {string} privateKey + * @property {string} publicKey + */ + const REASON_FAILED_TO_GET_KEYS = 'failed to get keys for accountId' const REASON_SKIPPED = 'skipped since it does not have a genesis key' const REASON_FAILED_TO_UPDATE_ACCOUNT = 'failed to update account keys' @@ -50,8 +57,8 @@ const REJECTED = 'rejected' export class AccountManager { /** * creates a new AccountManager instance - * @param logger the logger to use - * @param k8 the K8 instance + * @param {Logger} logger - the logger to use + * @param {K8} k8 - the K8 instance */ constructor (logger, k8) { if (!logger) throw new Error('An instance of core/Logger is required') @@ -60,15 +67,19 @@ export class AccountManager { this.logger = logger this.k8 = k8 this._portForwards = [] + + /** + * @type {NodeClient|null} + * @public + */ this._nodeClient = null } /** * Gets the account keys from the Kubernetes secret from which it is stored - * @param accountId the account ID for which we want its keys - * @param namespace the namespace that is storing the secret - * @returns {Promise<{accountId: string, privateKey: string, publicKey: string}|null>} a - * custom object with the account id, private key, and public key + * @param {string} accountId - the account ID for which we want its keys + * @param {string} namespace - the namespace that is storing the secret + * @returns {Promise} */ async getAccountKeysFromSecret (accountId, namespace) { const secret = await this.k8.getSecret(namespace, Templates.renderAccountKeySecretLabelSelector(accountId)) @@ -92,8 +103,8 @@ export class AccountManager { * Gets the treasury account private key from Kubernetes secret if it exists, else * returns the Genesis private key, then will return an AccountInfo object with the * accountId, privateKey, publicKey - * @param namespace the namespace that the secret is in - * @returns {Promise<{accountId: string, privateKey: string, publicKey: string}>} + * @param {string} namespace - the namespace that the secret is in + * @returns {Promise} */ async getTreasuryAccountKeys (namespace) { // check to see if the treasure account is in the secrets @@ -102,7 +113,8 @@ export class AccountManager { /** * batch up the accounts into sets to be processed - * @returns an array of arrays of numbers representing the accounts to update + * @param {number[][]} [accountRange] + * @returns {number[][]} an array of arrays of numbers representing the accounts to update */ batchAccounts (accountRange = constants.SYSTEM_ACCOUNTS) { const batchSize = constants.ACCOUNT_UPDATE_BATCH_SIZE @@ -150,8 +162,8 @@ export class AccountManager { /** * loads and initializes the Node Client - * @param namespace the namespace of the network - * @returns {Promise} + * @param {string} namespace - the namespace of the network + * @returns {Promise} */ async loadNodeClient (namespace) { if (!this._nodeClient || this._nodeClient.isClientShutDown) { @@ -210,10 +222,10 @@ export class AccountManager { /** * Returns a node client that can be used to make calls against - * @param namespace the namespace for which the node client resides - * @param {Map}networkNodeServicesMap a map of the service objects that proxy the nodes - * @param operatorId the account id of the operator of the transactions - * @param operatorKey the private key of the operator of the transactions + * @param {string} namespace - the namespace for which the node client resides + * @param {Map} networkNodeServicesMap - a map of the service objects that proxy the nodes + * @param {string} operatorId - the account id of the operator of the transactions + * @param {string} operatorKey - the private key of the operator of the transactions * @returns {Promise} a node client that can be used to call transactions */ async _getNodeClient (namespace, networkNodeServicesMap, operatorId, operatorKey) { @@ -253,8 +265,8 @@ export class AccountManager { /** * Gets a Map of the Hedera node services and the attributes needed - * @param namespace the namespace of the fullstack network deployment - * @returns {Promise>} a map of the network node services + * @param {string} namespace - the namespace of the fullstack network deployment + * @returns {Promise>} a map of the network node services */ async getNodeServiceMap (namespace) { const labelSelector = 'fullstack.hedera.com/node-name' @@ -330,12 +342,11 @@ export class AccountManager { } /** - * updates a set of special accounts keys with a newly generated key and stores them in a - * Kubernetes secret - * @param namespace the namespace of the nodes network - * @param currentSet the accounts to update - * @param updateSecrets whether to delete the secret prior to creating a new secret - * @param resultTracker an object to keep track of the results from the accounts that are being updated + * updates a set of special accounts keys with a newly generated key and stores them in a Kubernetes secret + * @param {string} namespace the namespace of the nodes network + * @param {string[]} currentSet - the accounts to update + * @param {boolean} updateSecrets - whether to delete the secret prior to creating a new secret + * @param {Object} resultTracker - an object to keep track of the results from the accounts that are being updated * @returns {Promise<*>} the updated resultTracker object */ async updateSpecialAccountsKeys (namespace, currentSet, updateSecrets, resultTracker) { @@ -374,12 +385,11 @@ export class AccountManager { } /** - * update the account keys for a given account and store its new key in a Kubernetes - * secret - * @param namespace the namespace of the nodes network - * @param accountId the account that will get its keys updated - * @param genesisKey the genesis key to compare against - * @param updateSecrets whether to delete the secret prior to creating a new secret + * update the account keys for a given account and store its new key in a Kubernetes secret + * @param {string} namespace - the namespace of the nodes network + * @param {AccountId} accountId - the account that will get its keys updated + * @param {PrivateKey} genesisKey - the genesis key to compare against + * @param {boolean} updateSecrets - whether to delete the secret prior to creating a new secret * @returns {Promise<{value: string, status: string}|{reason: string, value: string, status: string}>} the result of the call */ async updateAccountKeys (namespace, accountId, genesisKey, updateSecrets) { @@ -468,7 +478,7 @@ export class AccountManager { /** * gets the account info from Hedera network - * @param accountId the account + * @param {AccountId|string} accountId - the account * @returns {AccountInfo} the private key of the account */ async accountInfoQuery (accountId) { @@ -485,7 +495,7 @@ export class AccountManager { /** * gets the account private and public key from the Kubernetes secret from which it is stored - * @param accountId the account + * @param {AccountId|string} accountId - the account * @returns {Promise} the private key of the account */ async getAccountKeys (accountId) { @@ -503,9 +513,9 @@ export class AccountManager { /** * send an account key update transaction to the network of nodes - * @param accountId the account that will get it's keys updated - * @param newPrivateKey the new private key - * @param oldPrivateKey the genesis key that is the current key + * @param {AccountId|string} accountId - the account that will get its keys updated + * @param {PrivateKey|string} newPrivateKey - the new private key + * @param {PrivateKey|string} oldPrivateKey - the genesis key that is the current key * @returns {Promise} whether the update was successful */ async sendAccountKeyUpdate (accountId, newPrivateKey, oldPrivateKey) { @@ -538,13 +548,13 @@ export class AccountManager { /** * creates a new Hedera account - * @param namespace the namespace to store the Kubernetes key secret into - * @param privateKey the private key of type PrivateKey - * @param amount the amount of HBAR to add to the account - * @param setAlias whether to set the alias of the account to the public key, - * requires the privateKey supplied to be ECDSA - * @returns {{accountId: AccountId, privateKey: string, publicKey: string, balance: number}} a - * custom object with the account information in it + * @param {string} namespace - the namespace to store the Kubernetes key secret into + * @param {Key} privateKey - the private key of type PrivateKey + * @param {number} amount - the amount of HBAR to add to the account + * @param {boolean} [setAlias] - whether to set the alias of the account to the public key, requires + * the privateKey supplied to be ECDSA + * @returns {Promise<{accountId: AccountId, privateKey: string, publicKey: string, balance: number}>} a custom object with + * the account information in it */ async createNewAccount (namespace, privateKey, amount, setAlias = false) { const newAccountTransaction = new AccountCreateTransaction() @@ -601,9 +611,9 @@ export class AccountManager { /** * transfer the specified amount of HBAR from one account to another - * @param fromAccountId the account to pull the HBAR from - * @param toAccountId the account to put the HBAR - * @param hbarAmount the amount of HBAR + * @param {AccountId|string} fromAccountId - the account to pull the HBAR from + * @param {AccountId|string} toAccountId - the account to put the HBAR + * @param {number} hbarAmount - the amount of HBAR * @returns {Promise} if the transaction was successfully posted */ async transferAmount (fromAccountId, toAccountId, hbarAmount) { @@ -629,7 +639,7 @@ export class AccountManager { /** * Fetch and prepare address book as a base64 string * @param {string} namespace the namespace of the network - * @return {Promise} + * @returns {Promise} */ async prepareAddressBookBase64 (namespace) { // fetch AddressBook diff --git a/src/core/chart_manager.mjs b/src/core/chart_manager.mjs index 4c14b1683..f97c8bb20 100644 --- a/src/core/chart_manager.mjs +++ b/src/core/chart_manager.mjs @@ -14,11 +14,16 @@ * limitations under the License. * */ +'use strict' import { constants } from './index.mjs' import chalk from 'chalk' import { FullstackTestingError } from './errors.mjs' export class ChartManager { + /** + * @param {Helm} helm + * @param {Logger} logger + */ constructor (helm, logger) { if (!logger) throw new Error('An instance of core/Logger is required') if (!helm) throw new Error('An instance of core/Helm is required') @@ -32,8 +37,8 @@ export class ChartManager { * * 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 + * @param {Map} repoURLs - a map of name and chart repository URLs + * @param {boolean} force - whether or not to update the repo * @returns {Promise} */ async setup (repoURLs = constants.DEFAULT_CHART_REPO, force = true) { @@ -58,6 +63,7 @@ export class ChartManager { /** * List available clusters + * @param {string} namespaceName * @returns {Promise} */ async getInstalledCharts (namespaceName) { @@ -70,6 +76,14 @@ export class ChartManager { return [] } + /** + * @param {string} namespaceName + * @param {string} chartReleaseName + * @param {string} chartPath + * @param {string} version + * @param {string} valuesArg + * @returns {Promise} + */ async install (namespaceName, chartReleaseName, chartPath, version, valuesArg = '') { try { const isInstalled = await this.isChartInstalled(namespaceName, chartReleaseName) @@ -97,6 +111,11 @@ export class ChartManager { return true } + /** + * @param {string} namespaceName + * @param {string} chartReleaseName + * @returns {Promise} + */ async isChartInstalled (namespaceName, chartReleaseName) { this.logger.debug(`> checking if chart is installed [ chart: ${chartReleaseName}, namespace: ${namespaceName} ]`) const charts = await this.getInstalledCharts(namespaceName) @@ -109,6 +128,11 @@ export class ChartManager { return false } + /** + * @param {string} namespaceName + * @param {string} chartReleaseName + * @returns {Promise} + */ async uninstall (namespaceName, chartReleaseName) { try { const isInstalled = await this.isChartInstalled(namespaceName, chartReleaseName) @@ -126,6 +150,14 @@ export class ChartManager { return true } + /** + * @param {string} namespaceName + * @param {string} chartReleaseName + * @param {string} chartPath + * @param {string} valuesArg + * @param {string} version + * @returns {Promise} + */ async upgrade (namespaceName, chartReleaseName, chartPath, valuesArg = '', version = '') { let versionArg = '' if (version) { diff --git a/src/core/config_manager.mjs b/src/core/config_manager.mjs index f9bfa3fa7..28efacdf9 100644 --- a/src/core/config_manager.mjs +++ b/src/core/config_manager.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import fs from 'fs' import { FullstackTestingError, MissingArgumentError } from './errors.mjs' import { constants } from './index.mjs' @@ -29,6 +30,10 @@ import * as helpers from './helpers.mjs' * doesn't need to enter it repeatedly. However, user should still be able to specify the flag explicitly for any command. */ export class ConfigManager { + /** + * @param {Logger} logger + * @param {PathLike} cachedConfigFile + */ constructor (logger, cachedConfigFile = constants.SOLO_CONFIG_FILE) { if (!logger || !(logger instanceof Logger)) throw new MissingArgumentError('An instance of core/Logger is required') if (!cachedConfigFile) throw new MissingArgumentError('cached config file path is required') @@ -45,6 +50,8 @@ export class ConfigManager { try { if (fs.existsSync(this.cachedConfigFile)) { const configJSON = fs.readFileSync(this.cachedConfigFile) + + /** @type {Object} */ this.config = JSON.parse(configJSON.toString()) } } catch (e) { @@ -71,9 +78,9 @@ export class ConfigManager { * 2. Cached config value of the command flag. * 3. Default value of the command flag if the command is not 'init'. * - * @param argv yargs.argv - * @param aliases yargv.parsed.aliases - * @return {*} updated argv + * @param {yargs.argv} argv + * @param {yargv.parsed.aliases} aliases + * @returns {Object} updated argv */ applyPrecedence (argv, aliases) { for (const key of Object.keys(aliases)) { @@ -95,8 +102,8 @@ export class ConfigManager { /** * Update the config using the argv * - * @param argv list of yargs argv - * @param persist + * @param {Object} [argv] - list of yargs argv + * @param {boolean} persist */ update (argv = {}, persist = false) { if (argv && Object.keys(argv).length > 0) { @@ -178,8 +185,8 @@ export class ConfigManager { /** * Check if a flag value is set - * @param flag flag object - * @return {boolean} + * @param {{name: string}} flag flag object + * @returns {boolean} */ hasFlag (flag) { return this.config.flags[flag.name] !== undefined @@ -188,8 +195,8 @@ export class ConfigManager { /** * Return the value of the given flag * - * @param flag flag object - * @return {*|string} value of the flag or undefined if flag value is not available + * @param {{name: string}} flag flag object + * @returns {undefined|string} value of the flag or undefined if flag value is not available */ getFlag (flag) { if (this.config.flags[flag.name] !== undefined) { @@ -201,8 +208,8 @@ export class ConfigManager { /** * Set value for the flag - * @param flag flag object - * @param value value of the flag + * @param {{name: string}} flag - flag object + * @param value - value of the flag */ setFlag (flag, value) { @@ -212,7 +219,7 @@ export class ConfigManager { /** * Get package version - * @return {*} + * @returns {*} */ getVersion () { return this.config.version @@ -220,7 +227,7 @@ export class ConfigManager { /** * Get last updated at timestamp - * @return {string} + * @returns {string} */ getUpdatedAt () { return this.config.updatedAt diff --git a/src/core/constants.mjs b/src/core/constants.mjs index 6bfdd184a..66bf0c816 100644 --- a/src/core/constants.mjs +++ b/src/core/constants.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { AccountId, FileId } from '@hashgraph/sdk' import { color, PRESET_TIMER } from 'listr2' import path, { dirname, normalize } from 'path' @@ -59,6 +60,8 @@ export const JSON_RPC_RELAY_CHART_URL = 'https://hashgraph.github.io/hedera-json export const JSON_RPC_RELAY_CHART = 'hedera-json-rpc-relay' export const MIRROR_NODE_CHART_URL = 'https://hashgraph.github.io/hedera-mirror-node/charts' export const MIRROR_NODE_CHART = 'hedera-mirror' + +/** @type {Map} */ export const DEFAULT_CHART_REPO = new Map() .set(FULLSTACK_TESTING_CHART, FULLSTACK_TESTING_CHART_URL) .set(JSON_RPC_RELAY_CHART, JSON_RPC_RELAY_CHART_URL) @@ -78,6 +81,7 @@ export const TREASURY_ACCOUNT = 2 export const LOCAL_NODE_START_PORT = process.env.LOCAL_NODE_START_PORT || 30212 export const LOCAL_NODE_PROXY_START_PORT = process.env.LOCAL_NODE_PROXY_START_PORT || 30313 export const ACCOUNT_UPDATE_BATCH_SIZE = process.env.ACCOUNT_UPDATE_BATCH_SIZE || 10 + export const NODE_PROXY_USER_ID = process.env.NODE_PROXY_USER_ID || 'admin' export const NODE_PROXY_PASSWORD = process.env.NODE_PROXY_PASSWORD || 'adminpwd' @@ -89,7 +93,10 @@ export const POD_CONDITION_READY = 'Ready' export const POD_CONDITION_POD_SCHEDULED = 'PodScheduled' export const POD_CONDITION_STATUS_TRUE = 'True' -// Listr related +/** + * Listr related + * @type {LoggerFieldFn<[number]> & {condition: (duration: number) => boolean, format: (duration: number) => Color}} + */ export const LISTR_DEFAULT_RENDERER_TIMER_OPTION = { ...PRESET_TIMER, condition: (duration) => duration > 100, diff --git a/src/core/dependency_managers/dependency_manager.mjs b/src/core/dependency_managers/dependency_manager.mjs index aa7cc21c1..3c7de638c 100644 --- a/src/core/dependency_managers/dependency_manager.mjs +++ b/src/core/dependency_managers/dependency_manager.mjs @@ -14,11 +14,16 @@ * limitations under the License. * */ +'use strict' import os from 'os' import { FullstackTestingError, MissingArgumentError } from '../errors.mjs' import { ShellRunner } from '../shell_runner.mjs' export class DependencyManager extends ShellRunner { + /** + * @param {Logger} logger + * @param {Map} depManagerMap + */ constructor (logger, depManagerMap) { if (!logger) throw new MissingArgumentError('an instance of core/Logger is required', logger) super(logger) @@ -28,8 +33,8 @@ export class DependencyManager extends ShellRunner { /** * Check if the required dependency is installed or not - * @param dep is the name of the program - * @param shouldInstall Whether or not install the dependency if not installed + * @param {string} dep - is the name of the program + * @param {boolean} [shouldInstall] - Whether or not install the dependency if not installed * @returns {Promise} */ async checkDependency (dep, shouldInstall = true) { @@ -49,6 +54,10 @@ export class DependencyManager extends ShellRunner { return true } + /** + * @param {*[]} [deps] + * @returns {{title: string, task: () => Promise}[]} + */ taskCheckDependencies (deps = []) { const subTasks = [] deps.forEach(dep => { diff --git a/src/core/dependency_managers/helm_dependency_manager.mjs b/src/core/dependency_managers/helm_dependency_manager.mjs index 6403d285c..4e55af2ae 100644 --- a/src/core/dependency_managers/helm_dependency_manager.mjs +++ b/src/core/dependency_managers/helm_dependency_manager.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import fs from 'fs' import os from 'os' import path from 'path' @@ -29,6 +30,8 @@ import { OS_WIN32, OS_WINDOWS } from '../constants.mjs' // constants required by HelmDependencyManager const HELM_RELEASE_BASE_URL = 'https://get.helm.sh' const HELM_ARTIFACT_TEMPLATE = 'helm-%s-%s-%s.%s' + +/** @type {Map} */ const HELM_ARTIFACT_EXT = new Map() .set(constants.OS_DARWIN, 'tar.gz') .set(constants.OS_LINUX, 'tar.gz') @@ -38,6 +41,15 @@ const HELM_ARTIFACT_EXT = new Map() * Helm dependency manager installs or uninstalls helm client at SOLO_HOME_DIR/bin directory */ export class HelmDependencyManager extends ShellRunner { + /** + * @param {PackageDownloader} downloader + * @param {Zippy} zippy + * @param {Logger} logger + * @param {string} [installationDir] + * @param {NodeJS.Platform} [osPlatform] + * @param {string} [osArch] + * @param {string} [helmVersion] + */ constructor ( downloader, zippy, @@ -73,17 +85,23 @@ export class HelmDependencyManager extends ShellRunner { this.checksumURL = `${HELM_RELEASE_BASE_URL}/${this.artifactName}.sha256sum` } + /** + * @returns {string} + */ getHelmPath () { return this.helmPath } + /** + * @returns {boolean} + */ isInstalled () { return fs.existsSync(this.helmPath) } /** * Uninstall helm from solo bin folder - * @return {Promise} + * @returns {Promise} */ async uninstall () { if (this.isInstalled()) { @@ -91,6 +109,10 @@ export class HelmDependencyManager extends ShellRunner { } } + /** + * @param {string} [tmpDir] + * @returns {Promise} + */ async install (tmpDir = helpers.getTmpDir()) { const extractedDir = path.join(tmpDir, 'extracted-helm') let helmSrc = path.join(extractedDir, `${this.osPlatform}-${this.osArch}`, constants.HELM) @@ -120,6 +142,10 @@ export class HelmDependencyManager extends ShellRunner { return this.isInstalled() } + /** + * @param {boolean} [shouldInstall] + * @returns {Promise} + */ async checkVersion (shouldInstall = true) { if (!this.isInstalled()) { if (shouldInstall) { @@ -135,6 +161,9 @@ export class HelmDependencyManager extends ShellRunner { return semver.gte(parts[0], version.HELM_VERSION) } + /** + * @returns {string} + */ getHelmVersion () { return version.HELM_VERSION } diff --git a/src/core/dependency_managers/keytool_dependency_manager.mjs b/src/core/dependency_managers/keytool_dependency_manager.mjs index d4ea45f8c..8a71b41d8 100644 --- a/src/core/dependency_managers/keytool_dependency_manager.mjs +++ b/src/core/dependency_managers/keytool_dependency_manager.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import fs from 'fs' import os from 'os' import path from 'path' @@ -31,6 +32,15 @@ import { OS_WIN32, OS_WINDOWS } from '../constants.mjs' * Installs or uninstalls JRE client at SOLO_HOME_DIR/bin/jre directory */ export class KeytoolDependencyManager extends ShellRunner { + /** + * @param {PackageDownloader} downloader + * @param {Zippy} zippy + * @param {Logger} logger + * @param {string} [installationDir] + * @param {NodeJS.Platform} [osPlatform] + * @param {string} [osArch] + * @param {string} [javaVersion] + */ constructor ( downloader, zippy, @@ -74,6 +84,10 @@ export class KeytoolDependencyManager extends ShellRunner { this.keytoolPath = Templates.installationPath(constants.KEYTOOL, this.osPlatform, this.installationDir) } + /** + * @returns {Promise} + * @private + */ async _fetchKeytoolArtifactUrl () { const keytoolRelease = `jdk-${this.javaVersion.major}.${this.javaVersion.minor}.${this.javaVersion.patch}%2B${this.javaVersion.build}` const adoptiumURL = `https://api.adoptium.net/v3/assets/release_name/eclipse/${keytoolRelease}?architecture=${this.osArch}&heap_size=normal&image_type=jre&os=${this.osPlatform}&project=jdk` @@ -81,17 +95,23 @@ export class KeytoolDependencyManager extends ShellRunner { return data.binaries[0].package } + /** + * @returns {string} + */ getKeytoolPath () { return this.keytoolPath } + /** + * @returns {boolean} + */ isInstalled () { return fs.existsSync(this.keytoolPath) } /** * Uninstall keytool from solo bin folder - * @return {Promise} + * @returns {Promise} */ async uninstall () { if (fs.existsSync(this.jreDir)) { @@ -99,6 +119,10 @@ export class KeytoolDependencyManager extends ShellRunner { } } + /** + * @param {string} [tmpDir] + * @returns {Promise} + */ async install (tmpDir = helpers.getTmpDir()) { const extractedDir = path.join(tmpDir, 'extracted-keytool') if (!this.keytoolPackage) { @@ -143,6 +167,10 @@ export class KeytoolDependencyManager extends ShellRunner { return this.isInstalled() } + /** + * @param {boolean} [shouldInstall] + * @returns {Promise} + */ async checkVersion (shouldInstall = true) { if (!this.isInstalled()) { if (shouldInstall) { @@ -158,6 +186,9 @@ export class KeytoolDependencyManager extends ShellRunner { return semver.gte(parts[1], version.JAVA_VERSION) } + /** + * @returns {Keytool} + */ getKeytool () { if (this.keytool) { return this.keytool @@ -167,6 +198,9 @@ export class KeytoolDependencyManager extends ShellRunner { return this.keytool } + /** + * @returns {string} + */ getKeytoolVersion () { return version.JAVA_VERSION } diff --git a/src/core/errors.mjs b/src/core/errors.mjs index d20bd7b1e..e8e41a5c2 100644 --- a/src/core/errors.mjs +++ b/src/core/errors.mjs @@ -14,15 +14,17 @@ * limitations under the License. * */ +'use strict' + export class FullstackTestingError extends Error { /** * Create a custom error object * * error metadata will include the `cause` * - * @param message error message - * @param cause source error (if any) - * @param meta additional metadata (if any) + * @param {string} message error message + * @param {Error | Object} cause source error (if any) + * @param {Object} meta additional metadata (if any) */ constructor (message, cause = {}, meta = {}) { super(message) @@ -43,9 +45,9 @@ export class ResourceNotFoundError extends FullstackTestingError { * * error metadata will include `resource` * - * @param message error message - * @param resource name of the resource - * @param cause source error (if any) + * @param {string} message - error message + * @param {string} resource - name of the resource + * @param {Error|Object} cause - source error (if any) */ constructor (message, resource, cause = {}) { super(message, cause, { resource }) @@ -56,8 +58,8 @@ export class MissingArgumentError extends FullstackTestingError { /** * Create a custom error for missing argument scenario * - * @param message error message - * @param cause source error (if any) + * @param {string} message - error message + * @param {Error|Object} cause - source error (if any) */ constructor (message, cause = {}) { super(message, cause) @@ -70,9 +72,9 @@ export class IllegalArgumentError extends FullstackTestingError { * * error metadata will include `value` * - * @param message error message - * @param value value of the invalid argument - * @param cause source error (if any) + * @param {string} message - error message + * @param {*} value - value of the invalid argument + * @param {Error|Object} cause - source error (if any) */ constructor (message, value = '', cause = {}) { super(message, cause, { value }) @@ -85,10 +87,10 @@ export class DataValidationError extends FullstackTestingError { * * error metadata will include `expected` and `found` values. * - * @param message error message - * @param expected expected value - * @param found value found - * @param cause source error (if any) + * @param {string} message - error message + * @param {*} expected - expected value + * @param {*} found - value found + * @param {Error|Object} [cause] - source error (if any) */ constructor (message, expected, found, cause = {}) { super(message, cause, { expected, found }) diff --git a/src/core/helm.mjs b/src/core/helm.mjs index b0475be35..27e623df9 100644 --- a/src/core/helm.mjs +++ b/src/core/helm.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import os from 'os' import { constants } from './index.mjs' import { ShellRunner } from './shell_runner.mjs' @@ -21,6 +22,10 @@ import { Templates } from './templates.mjs' import { IllegalArgumentError } from './errors.mjs' export class Helm extends ShellRunner { + /** + * @param {Logger} logger + * @param {NodeJS.Platform} [osPlatform] + */ constructor (logger, osPlatform = os.platform()) { if (!logger) throw new IllegalArgumentError('an instance of core/Logger is required', logger) super(logger) @@ -30,8 +35,8 @@ export class Helm extends ShellRunner { /** * Prepare a `helm` shell command string - * @param action represents a helm command (e.g. create | install | get ) - * @param args args of the command + * @param {string} action - represents a helm command (e.g. create | install | get ) + * @param {string} args - args of the command * @returns {string} */ prepareCommand (action, ...args) { @@ -42,7 +47,7 @@ export class Helm extends ShellRunner { /** * Invoke `helm install` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async install (...args) { @@ -51,7 +56,7 @@ export class Helm extends ShellRunner { /** * Invoke `helm uninstall` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async uninstall (...args) { @@ -60,7 +65,7 @@ export class Helm extends ShellRunner { /** * Invoke `helm upgrade` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async upgrade (...args) { @@ -69,7 +74,7 @@ export class Helm extends ShellRunner { /** * Invoke `helm list` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async list (...args) { @@ -78,8 +83,8 @@ export class Helm extends ShellRunner { /** * Invoke `helm dependency` command - * @param subCommand sub-command - * @param args args of the command + * @param {string} subCommand - sub-command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async dependency (subCommand, ...args) { @@ -88,8 +93,8 @@ export class Helm extends ShellRunner { /** * Invoke `helm repo` command - * @param subCommand sub-command - * @param args args of the command + * @param {string} subCommand - sub-command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async repo (subCommand, ...args) { @@ -98,7 +103,8 @@ export class Helm extends ShellRunner { /** * Get helm version - * @return {Promise} + * @param {string[]} args + * @returns {Promise} */ async version (args = ['--short']) { return this.run(this.prepareCommand('version', ...args)) diff --git a/src/core/helpers.mjs b/src/core/helpers.mjs index 023382cee..5bfa0b211 100644 --- a/src/core/helpers.mjs +++ b/src/core/helpers.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import fs from 'fs' import os from 'os' import path from 'path' @@ -30,16 +31,29 @@ import { FileContentsQuery, FileId } from '@hashgraph/sdk' // cache current directory const CUR_FILE_DIR = paths.dirname(fileURLToPath(import.meta.url)) +/** + * @param {number} ms + * @returns {Promise} + */ export function sleep (ms) { return new Promise((resolve) => { setTimeout(resolve, ms) }) } +/** + * @param {string} input + * @returns {string[]} + */ export function parseNodeIds (input) { return splitFlagInput(input, ',') } +/** + * @param {string} input + * @param {string} separator + * @returns {string[]} + */ export function splitFlagInput (input, separator = ',') { if (typeof input === 'string') { const items = [] @@ -56,13 +70,18 @@ export function splitFlagInput (input, separator = ',') { throw new FullstackTestingError('input is not a comma separated string') } +/** + * @template T + * @param {T[]} arr - The array to be cloned + * @returns {T[]} A new array with the same elements as the input array + */ export function cloneArray (arr) { return JSON.parse(JSON.stringify(arr)) } /** * load package.json - * @returns {any} + * @returns {*} */ export function loadPackageJSON () { try { @@ -73,6 +92,9 @@ export function loadPackageJSON () { } } +/** + * @returns {string} + */ export function packageVersion () { const packageJson = loadPackageJSON() return packageJson.version @@ -80,8 +102,8 @@ export function packageVersion () { /** * Return the required root image for a platform version - * @param releaseTag platform version - * @return {string} + * @param {string} releaseTag - platform version + * @returns {string} */ export function getRootImageRepository (releaseTag) { const releaseVersion = semver.parse(releaseTag, { includePrerelease: true }) @@ -92,10 +114,19 @@ export function getRootImageRepository (releaseTag) { return 'hashgraph/full-stack-testing/ubi8-init-java21' } +/** + * @returns {string} + */ export function getTmpDir () { return fs.mkdtempSync(path.join(os.tmpdir(), 'solo-')) } +/** + * @param {string} destDir + * @param {string} prefix + * @param {Date} curDate + * @returns {string} + */ export function createBackupDir (destDir, prefix = 'backup', curDate = new Date()) { const dateDir = util.format('%s%s%s_%s%s%s', curDate.getFullYear(), @@ -114,6 +145,10 @@ export function createBackupDir (destDir, prefix = 'backup', curDate = new Date( return backupDir } +/** + * @param {Map} [fileMap] + * @param {boolean} removeOld + */ export function makeBackup (fileMap = new Map(), removeOld = true) { for (const entry of fileMap) { const srcPath = entry[0] @@ -127,6 +162,13 @@ export function makeBackup (fileMap = new Map(), removeOld = true) { } } +/** + * @param {string[]} nodeIds + * @param {string} keysDir + * @param {Date} curDate + * @param {string} dirPrefix + * @returns {string} + */ export function backupOldPfxKeys (nodeIds, keysDir, curDate = new Date(), dirPrefix = 'gossip-pfx') { const backupDir = createBackupDir(keysDir, `unused-${dirPrefix}`, curDate) const fileMap = new Map() @@ -144,6 +186,13 @@ export function backupOldPfxKeys (nodeIds, keysDir, curDate = new Date(), dirPre return backupDir } +/** + * @param {string[]} nodeIds + * @param {string} keysDir + * @param {Date} curDate + * @param {string} dirPrefix + * @returns {string} + */ export function backupOldTlsKeys (nodeIds, keysDir, curDate = new Date(), dirPrefix = 'tls') { const backupDir = createBackupDir(keysDir, `unused-${dirPrefix}`, curDate) const fileMap = new Map() @@ -158,6 +207,13 @@ export function backupOldTlsKeys (nodeIds, keysDir, curDate = new Date(), dirPre return backupDir } +/** + * @param {string[]} nodeIds + * @param {string} keysDir + * @param {Date} curDate + * @param {string} dirPrefix + * @returns {string} + */ export function backupOldPemKeys (nodeIds, keysDir, curDate = new Date(), dirPrefix = 'gossip-pem') { const backupDir = createBackupDir(keysDir, `unused-${dirPrefix}`, curDate) const fileMap = new Map() @@ -172,6 +228,10 @@ export function backupOldPemKeys (nodeIds, keysDir, curDate = new Date(), dirPre return backupDir } +/** + * @param {string} str + * @returns {boolean} + */ export function isNumeric (str) { if (typeof str !== 'string') return false // we only process strings! return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... @@ -180,8 +240,8 @@ export function isNumeric (str) { /** * Validate a path provided by the user to prevent path traversal attacks - * @param input the input provided by the user - * @returns {*} a validated path + * @param {string} input - the input provided by the user + * @returns {string} a validated path */ export function validatePath (input) { if (input.indexOf('\0') !== -1) { @@ -193,8 +253,8 @@ export function validatePath (input) { /** * Download logs files from all network pods and save to local solo log directory * an instance of core/K8 - * @param {K8} k8 an instance of core/K8 - * @param {string} namespace the namespace of the network + * @param {K8} k8 - an instance of core/K8 + * @param {string} namespace - the namespace of the network * @returns {Promise} A promise that resolves when the logs are downloaded */ export async function getNodeLogs (k8, namespace) { @@ -227,8 +287,8 @@ export async function getNodeLogs (k8, namespace) { /** * Create a map of node IDs to account IDs - * @param nodeIDs an array of the node IDs - * @returns {Map} the map of node IDs to account IDs + * @param {string[]} nodeIDs + * @returns {Map} the map of node IDs to account IDs */ export function getNodeAccountMap (nodeIDs) { const accountMap = /** @type {Map} **/ new Map() @@ -243,6 +303,12 @@ export function getNodeAccountMap (nodeIDs) { return accountMap } +/** + * @param {AccountManager} accountManager + * @param {string} namespace + * @param {number} fileNum + * @returns {Promise} + */ export async function getFileContents (accountManager, namespace, fileNum) { await accountManager.loadNodeClient(namespace) const client = accountManager._nodeClient @@ -251,11 +317,20 @@ export async function getFileContents (accountManager, namespace, fileNum) { return Buffer.from(await queryFees.execute(client)).toString('hex') } +/** + * @param {Array} envVarArray + * @param {string} name + * @returns {string|null} + */ export function getEnvValue (envVarArray, name) { const kvPair = envVarArray.find(v => v.startsWith(`${name}=`)) return kvPair ? kvPair.split('=')[1] : null } +/** + * @param {string} ipAddress + * @returns {Uint8Array} + */ export function parseIpAddressToUint8Array (ipAddress) { const parts = ipAddress.split('.') const uint8Array = new Uint8Array(4) diff --git a/src/core/k8.mjs b/src/core/k8.mjs index aa51bd07a..e15597b09 100644 --- a/src/core/k8.mjs +++ b/src/core/k8.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import * as k8s from '@kubernetes/client-node' import fs from 'fs' import net from 'net' @@ -37,6 +38,10 @@ import { constants } from './index.mjs' export class K8 { static PodReadyCondition = new Map().set(constants.POD_CONDITION_READY, constants.POD_CONDITION_STATUS_TRUE) + /** + * @param {ConfigManager} configManager + * @param {Logger} logger + */ constructor (configManager, logger) { if (!configManager) throw new MissingArgumentError('An instance of core/ConfigManager is required') if (!logger) throw new MissingArgumentError('An instance of core/Logger is required') @@ -51,17 +56,23 @@ export class K8 { * Clone a new instance with the same config manager and logger * Internally it instantiates a new kube API client * - * @return {K8} + * @returns {K8} */ clone () { const c = new K8(this.configManager, this.logger) return c.init() } + /** + * @returns {k8s.KubeConfig} + */ getKubeConfig () { return this.kubeConfig } + /** + * @returns {K8} + */ init () { this.kubeConfig = new k8s.KubeConfig() this.kubeConfig.loadFromDefault() @@ -83,9 +94,9 @@ export class K8 { /** * Apply filters to metadata - * @param items list of items - * @param filters an object with metadata fields and value - * @return a list of items that match the filters + * @param {Object[]} items - list of items + * @param {Object} [filters] - an object with metadata fields and value + * @returns {Object[]} a list of items that match the filters */ applyMetadataFilter (items, filters = {}) { if (!filters) throw new MissingArgumentError('filters are required') @@ -115,9 +126,9 @@ export class K8 { /** * Filter a single item using metadata filter - * @param items list of items - * @param filters an object with metadata fields and value - * @return {*} + * @param {Object[]} items - list of items + * @param {Object} [filters] - an object with metadata fields and value + * @returns {Object} */ filterItem (items, filters = {}) { const filtered = this.applyMetadataFilter(items, filters) @@ -127,8 +138,8 @@ export class K8 { /** * Create a new namespace - * @param name name of the namespace - * @return {Promise} + * @param {string} name - name of the namespace + * @returns {Promise} */ async createNamespace (name) { const payload = { @@ -143,8 +154,8 @@ export class K8 { /** * Delete a namespace - * @param name name of the namespace - * @return {Promise} + * @param {string} name - name of the namespace + * @returns {Promise} */ async deleteNamespace (name) { const resp = await this.kubeClient.deleteNamespace(name) @@ -153,7 +164,7 @@ export class K8 { /** * Get a list of namespaces - * @return list of namespaces + * @returns {string[]} list of namespaces */ async getNamespaces () { const resp = await this.kubeClient.listNamespace() @@ -171,8 +182,8 @@ export class K8 { /** * Returns true if a namespace exists with the given name - * @param namespace namespace name - * @return {Promise} + * @param {string} namespace namespace name + * @returns {Promise} */ async hasNamespace (namespace) { const namespaces = await this.getNamespaces() @@ -181,8 +192,8 @@ export class K8 { /** * Get a podName by name - * @param name podName name - * @return {Promise<{}>} k8s.V1Pod object + * @param {string} name - podName name + * @returns {Promise} k8s.V1Pod object */ async getPodByName (name) { const ns = this._getNamespace() @@ -200,8 +211,8 @@ export class K8 { /** * Get pods by labels - * @param labels list of labels - * @return {Promise>} + * @param {string[]} labels - list of labels + * @returns {Promise>} */ async getPodsByLabel (labels = []) { const ns = this._getNamespace() @@ -220,8 +231,8 @@ export class K8 { /** * Get secrets by labels - * @param labels list of labels - * @return {Promise>} + * @param {string[]} labels - list of labels + * @returns {Promise>} */ async getSecretsByLabel (labels = []) { const ns = this._getNamespace() @@ -240,7 +251,7 @@ export class K8 { /** * Get host IP of a podName - * @param podNameName name of the podName + * @param {string} podNameName - name of the podName * @returns {Promise} podName IP */ async getPodIP (podNameName) { @@ -256,8 +267,8 @@ export class K8 { /** * Get a svc by name - * @param name svc name - * @return {Promise<{}>} k8s.V1Service object + * @param {string} name - svc name + * @returns {Promise} k8s.V1Service object */ async getSvcByName (name) { const ns = this._getNamespace() @@ -275,7 +286,7 @@ export class K8 { /** * Get cluster IP of a service - * @param svcName name of the service + * @param {string} svcName - name of the service * @returns {Promise} cluster IP */ async getClusterIP (svcName) { @@ -289,7 +300,7 @@ export class K8 { /** * Get a list of clusters - * @return a list of cluster names + * @returns {string[]} a list of cluster names */ async getClusters () { const clusters = [] @@ -302,7 +313,7 @@ export class K8 { /** * Get a list of contexts - * @return a list of context names + * @returns {string[]} a list of context names */ async getContexts () { const contexts = [] @@ -327,11 +338,12 @@ export class K8 { * name: config.txt * }] * - * @param podName pod name - * @param containerName container name - * @param destPath path inside the container - * @param timeout timeout in ms - * @return {Promise} array of directory entries, custom object + * @param {string} podName + * @param {string} containerName + * @param {string} destPath - path inside the container + * @param {number} [timeout] - timeout in ms + * @returns {Promise<{owner: string, size: number, modifiedAt: string, name: string, directory: boolean, group: string}[]>} + * array of directory entries, custom object */ async listDir (podName, containerName, destPath, timeout = 5000) { try { @@ -375,11 +387,11 @@ export class K8 { /** * Check if a filepath exists in the container - * @param podName pod name - * @param containerName container name - * @param destPath path inside the container - * @param filters an object with metadata fields and value - * @return {Promise} + * @param {string} podName + * @param {string} containerName + * @param {string} destPath - path inside the container + * @param {Object} [filters] - an object with metadata fields and value + * @returns {Promise} */ async hasFile (podName, containerName, destPath, filters = {}) { const parentDir = path.dirname(destPath) @@ -420,10 +432,10 @@ export class K8 { /** * Check if a directory path exists in the container - * @param podName pod name - * @param containerName container name - * @param destPath path inside the container - * @return {Promise} + * @param {string} podName + * @param {string} containerName + * @param {string} destPath - path inside the container + * @returns {Promise} */ async hasDir (podName, containerName, destPath) { return await this.execContainer( @@ -433,6 +445,12 @@ export class K8 { ) === 'true' } + /** + * @param {string} podName + * @param {string} containerName + * @param {string} destPath + * @returns {Promise} + */ async mkdir (podName, containerName, destPath) { return this.execContainer( podName, @@ -446,11 +464,11 @@ export class K8 { * * It overwrites any existing file inside the container at the destination directory * - * @param podName podName name - * @param containerName container name - * @param srcPath source file path in the local - * @param destDir destination directory in the container - * @returns return a Promise that performs the copy operation + * @param {string} podName + * @param {string} containerName + * @param {string} srcPath - source file path in the local + * @param {string} destDir - destination directory in the container + * @returns {Promise} return a Promise that performs the copy operation */ async copyTo (podName, containerName, srcPath, destDir) { const namespace = this._getNamespace() @@ -512,10 +530,10 @@ export class K8 { * * It overwrites any existing file at the destination directory * - * @param podName podName name - * @param containerName container name - * @param srcPath source file path in the container - * @param destDir destination directory in the local + * @param {string} podName + * @param {string} containerName + * @param {string} srcPath - source file path in the container + * @param {string} destDir - destination directory in the local * @returns {Promise} */ async copyFrom (podName, containerName, srcPath, destDir) { @@ -614,10 +632,10 @@ export class K8 { /** * Invoke sh command within a container and return the console output as string * - * @param podName pod name - * @param containerName container name - * @param command sh commands as an array to be run within the containerName (e.g 'ls -la /opt/hgcapp') - * @param timeoutMs timout in milliseconds + * @param {string} podName + * @param {string} containerName + * @param {string|string[]} command - sh commands as an array to be run within the containerName (e.g 'ls -la /opt/hgcapp') + * @param {number} [timeoutMs] - timout in milliseconds * @returns {Promise} console output as string */ async execContainer (podName, containerName, command, timeoutMs = 1000) { @@ -668,9 +686,10 @@ export class K8 { * This simple server just forwards traffic from itself to a service running in kubernetes * -> localhost:localPort -> port-forward-tunnel -> kubernetes-pod:targetPort * - * @param podName pod name - * @param localPort local port - * @param podPort port of the pod + * @param {string} podName + * @param {number} localPort + * @param {number} podPort + * @returns {Promise} */ async portForward (podName, localPort, podPort) { const ns = this._getNamespace() @@ -688,8 +707,8 @@ export class K8 { /** * to test the connection to a pod within the network - * @param host the host of the target connection - * @param port the port of the target connection + * @param {string} host - the host of the target connection + * @param {number} port - the port of the target connection * @returns {Promise} */ async testConnection (host, port) { @@ -713,10 +732,10 @@ export class K8 { /** * Stop the port forwarder server * - * @param server an instance of server returned by portForward method - * @param maxAttempts the maximum number of attempts to check if the server is stopped - * @param timeout the delay between checks in milliseconds - * @return {Promise} + * @param {net.Server} server - an instance of server returned by portForward method + * @param {number} [maxAttempts] - the maximum number of attempts to check if the server is stopped + * @param {number} [timeout] - the delay between checks in milliseconds + * @returns {Promise} */ async stopPortForward (server, maxAttempts = 20, timeout = 500) { if (!server) { @@ -786,13 +805,13 @@ export class K8 { /** * Wait for pod - * @param phases an array of acceptable phases of the pods - * @param labels pod labels - * @param podCount number of pod expected - * @param maxAttempts maximum attempts to check - * @param delay delay between checks in milliseconds - * @param podItemPredicate a predicate function to check the pod item - * @return a Promise that checks the status of an array of pods + * @param {string[]} [phases] - an array of acceptable phases of the pods + * @param {string[]} [labels] - pod labels + * @param {number} [podCount] - number of pod expected + * @param {number} [maxAttempts] - maximum attempts to check + * @param {number} [delay] - delay between checks in milliseconds + * @param {Function} podItemPredicate - a predicate function to check the pod item + * @returns {Promise} a Promise that checks the status of an array of pods */ async waitForPods (phases = [constants.POD_PHASE_RUNNING], labels = [], podCount = 1, maxAttempts = 10, delay = 500, podItemPredicate) { const ns = this._getNamespace() @@ -850,11 +869,11 @@ export class K8 { /** * Check if pod is ready - * @param labels pod labels - * @param podCount number of pod expected - * @param maxAttempts maximum attempts to check - * @param delay delay between checks in milliseconds - * @return {Promise} + * @param {string[]} [labels] - pod labels + * @param {number} [podCount] - number of pod expected + * @param {number} [maxAttempts] - maximum attempts to check + * @param {number} [delay] - delay between checks in milliseconds + * @returns {Promise} */ async waitForPodReady (labels = [], podCount = 1, maxAttempts = 10, delay = 500) { try { @@ -866,14 +885,13 @@ export class K8 { /** * Check pods for conditions - * @param conditionsMap a map of conditions and values - * @param labels pod labels - * @param podCount number of pod expected - * @param maxAttempts maximum attempts to check - * @param delay delay between checks in milliseconds - * @return {Promise} + * @param {Map} conditionsMap - a map of conditions and values + * @param {string[]} [labels] - pod labels + * @param {number} [podCount] - number of pod expected + * @param {number} [maxAttempts] - maximum attempts to check + * @param {number} [delay] - delay between checks in milliseconds + * @returns {Promise} */ - async waitForPodConditions ( conditionsMap, labels = [], @@ -901,9 +919,9 @@ export class K8 { /** * Get a list of persistent volume claim names for the given namespace - * @param namespace the namespace of the persistent volume claims to return - * @param labels labels - * @returns return list of persistent volume claim names + * @param {string} namespace - the namespace of the persistent volume claims to return + * @param {string[]} [labels] - labels + * @returns {Promise} return list of persistent volume claim names */ async listPvcsByNamespace (namespace, labels = []) { const pvcs = [] @@ -926,9 +944,9 @@ export class K8 { /** * Get a list of secrets for the given namespace - * @param namespace the namespace of the secrets to return - * @param labels labels - * @returns return list of secret names + * @param {string} namespace - the namespace of the secrets to return + * @param {string[]} [labels] - labels + * @returns {Promise} return list of secret names */ async listSecretsByNamespace (namespace, labels = []) { const secrets = [] @@ -951,8 +969,8 @@ export class K8 { /** * Delete a persistent volume claim - * @param name the name of the persistent volume claim to delete - * @param namespace the namespace of the persistent volume claim to delete + * @param {string} name - the name of the persistent volume claim to delete + * @param {string} namespace - the namespace of the persistent volume claim to delete * @returns {Promise} true if the persistent volume claim was deleted */ async deletePvc (name, namespace) { @@ -966,10 +984,10 @@ export class K8 { /** * retrieve the secret of the given namespace and label selector, if there is more than one, it returns the first - * @param namespace the namespace of the secret to search for - * @param labelSelector the label selector used to fetch the Kubernetes secret - * @returns a custom secret object with the relevant attributes, the values of the data key:value pair - * objects must be base64 decoded + * @param {string} namespace - the namespace of the secret to search for + * @param {string} labelSelector - the label selector used to fetch the Kubernetes secret + * @returns {Promise<{name: string, labels: Object, namespace: string, type: string, data: Object} | null>} a custom + * secret object with the relevant attributes, the values of the data key:value pair objects must be base64 decoded */ async getSecret (namespace, labelSelector) { const result = await this.kubeClient.listNamespacedSecret( @@ -990,19 +1008,19 @@ export class K8 { /** * creates a new Kubernetes secret with the provided attributes - * @param name the name of the new secret - * @param namespace the namespace to store the secret - * @param secretType the secret type - * @param data the secret, any values of a key:value pair must be base64 encoded - * @param labels the label to use for future label selector queries - * @param recreate if we should first run delete in the case that there the secret exists from a previous install + * @param {string} name - the name of the new secret + * @param {string} namespace - the namespace to store the secret + * @param {string} secretType - the secret type + * @param {Object} data - the secret, any values of a key:value pair must be base64 encoded + * @param {*} labels - the label to use for future label selector queries + * @param {boolean} recreate - if we should first run delete in the case that there the secret exists from a previous install * @returns {Promise} whether the secret was created successfully */ async createSecret (name, namespace, secretType, data, labels, recreate) { if (recreate) { try { await this.kubeClient.deleteNamespacedSecret(name, namespace) - } catch (e) { + } catch { // do nothing } } @@ -1027,8 +1045,8 @@ export class K8 { /** * delete a secret from the namespace - * @param name the name of the new secret - * @param namespace the namespace to store the secret + * @param {string} name - the name of the new secret + * @param {string} namespace - the namespace to store the secret * @returns {Promise} whether the secret was deleted successfully */ async deleteSecret (name, namespace) { @@ -1036,17 +1054,30 @@ export class K8 { return resp.response.statusCode === 200.0 } + /** + * @returns {string} + * @private + */ _getNamespace () { const ns = this.configManager.getFlag(flags.namespace) if (!ns) throw new MissingArgumentError('namespace is not set') return ns } + /** + * @param {string} fileName + * @returns {string} + * @private + */ _tempFileFor (fileName) { const tmpFile = `${fileName}-${uuid4()}` return path.join(os.tmpdir(), tmpFile) } + /** + * @param {string} tmpFile + * @private + */ _deleteTempFile (tmpFile) { if (fs.existsSync(tmpFile)) { fs.rmSync(tmpFile) diff --git a/src/core/key_manager.mjs b/src/core/key_manager.mjs index 940233d31..bc0b82176 100644 --- a/src/core/key_manager.mjs +++ b/src/core/key_manager.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import * as x509 from '@peculiar/x509' import crypto from 'crypto' import fs from 'fs' @@ -32,6 +33,19 @@ import chalk from 'chalk' x509.cryptoProvider.set(crypto) +/** + * @typedef {Object} NodeKeyObject + * @property {CryptoKey} privateKey + * @property {x509.X509Certificate} certificate + * @property {x509.X509Certificates} certificateChain + */ + +/** + * @typedef {Object} PrivateKeyAndCertificateObject + * @property {string} privateKeyFile + * @property {string} certificateFile + */ + export class KeyManager { static SigningKeyAlgo = { name: 'RSASSA-PKCS1-v1_5', @@ -66,6 +80,9 @@ export class KeyManager { hash: 'SHA-384' } + /** + * @param {Logger} logger + */ constructor (logger) { if (!logger || !(logger instanceof Logger)) throw new MissingArgumentError('An instance of core/Logger is required') this.logger = logger @@ -73,7 +90,7 @@ export class KeyManager { /** * Convert CryptoKey into PEM string - * @param privateKey + * @param {CryptoKey} privateKey * @returns {Promise} */ async convertPrivateKeyToPem (privateKey) { @@ -83,9 +100,9 @@ export class KeyManager { /** * Convert PEM private key into CryptoKey - * @param pemStr PEM string - * @param algo key algorithm - * @param keyUsages key usages + * @param {string} pemStr - PEM string + * @param {*} algo - key algorithm + * @param {string[]} [keyUsages] * @returns {Promise} */ async convertPemToPrivateKey (pemStr, algo, keyUsages = ['sign']) { @@ -104,10 +121,10 @@ export class KeyManager { /** * Return file names for node key - * @param nodeId node ID - * @param keyPrefix key prefix such as constants.PFX_AGREEMENT_KEY_PREFIX - * @param keysDir directory where keys and certs are stored - * @returns {{privateKeyFile: string, certificateFile: string}} + * @param {string} nodeId + * @param {string} keysDir - directory where keys and certs are stored + * @param {string} [keyPrefix] - key prefix such as constants.PFX_AGREEMENT_KEY_PREFIX + * @returns {PrivateKeyAndCertificateObject} */ prepareNodeKeyFilePaths (nodeId, keysDir, keyPrefix = constants.SIGNING_KEY_PREFIX) { if (!nodeId) throw new MissingArgumentError('nodeId is required') @@ -125,9 +142,9 @@ export class KeyManager { /** * Return file names for TLS key - * @param nodeId node ID - * @param keysDir directory where keys and certs are stored - * @returns {{privateKeyFile: string, certificateFile: string}} + * @param {string} nodeId + * @param {string} keysDir - directory where keys and certs are stored + * @returns {PrivateKeyAndCertificateObject} */ prepareTLSKeyFilePaths (nodeId, keysDir) { if (!nodeId) throw new MissingArgumentError('nodeId is required') @@ -144,12 +161,12 @@ export class KeyManager { /** * Store node keys and certs as PEM files - * @param nodeId node ID - * @param nodeKey an object containing privateKeyPem, certificatePem data - * @param keysDir directory where keys and certs are stored - * @param nodeKeyFiles an object stores privateKeyFile and certificateFile - * @param keyName optional key type name for logging - * @return a Promise that saves the keys and certs as PEM files + * @param {string} nodeId + * @param {NodeKeyObject} nodeKey + * @param {string} keysDir - directory where keys and certs are stored + * @param {PrivateKeyAndCertificateObject} nodeKeyFiles + * @param {string} [keyName] - optional key type name for logging + * @returns {Promise} a Promise that saves the keys and certs as PEM files */ async storeNodeKey (nodeId, nodeKey, keysDir, nodeKeyFiles, keyName = '') { if (!nodeId) { @@ -211,12 +228,12 @@ export class KeyManager { /** * Load node keys and certs from PEM files - * @param nodeId node ID - * @param keysDir directory where keys and certs are stored - * @param algo algorithm used for key - * @param nodeKeyFiles an object stores privateKeyFile and certificateFile - * @param keyName optional key type name for logging - * @return returns a dictionary object contains privateKey, certificate, certificateChain + * @param {string} nodeId + * @param {string} keysDir - directory where keys and certs are stored + * @param {*} algo - algorithm used for key + * @param {{privateKeyFile: string, certificateFile: string}} nodeKeyFiles an object stores privateKeyFile and certificateFile + * @param {string} [keyName] - optional key type name for logging + * @returns {Promise} */ async loadNodeKey (nodeId, keysDir, algo, nodeKeyFiles, keyName = '') { if (!nodeId) { @@ -248,6 +265,7 @@ export class KeyManager { const certBytes = await fs.readFileSync(nodeKeyFiles.certificateFile) const certPems = x509.PemConverter.decode(certBytes.toString()) + /** @type {x509.X509Certificate[]} */ const certs = [] certPems.forEach(certPem => { const cert = new x509.X509Certificate(certPem) @@ -269,8 +287,8 @@ export class KeyManager { /** * Generate signing key and certificate - * @param nodeId node ID - * @return returns a dictionary object stores privateKey, certificate, certificateChain + * @param {string} nodeId + * @returns {Promise<{NodeKeyObject>} */ async generateSigningKey (nodeId) { try { @@ -315,10 +333,10 @@ export class KeyManager { /** * Store signing key and certificate - * @param nodeId node ID - * @param nodeKey an object containing privateKeyPem, certificatePem data - * @param keysDir directory where keys and certs are stored - * @return returns a Promise that saves the keys and certs as PEM files + * @param {string} nodeId + * @param {NodeKeyObject} nodeKey - an object containing privateKeyPem, certificatePem data + * @param {string} keysDir - directory where keys and certs are stored + * @returns {Promise<*>} returns a Promise that saves the keys and certs as PEM files */ async storeSigningKey (nodeId, nodeKey, keysDir) { const nodeKeyFiles = this.prepareNodeKeyFilePaths(nodeId, keysDir, constants.SIGNING_KEY_PREFIX) @@ -327,9 +345,9 @@ export class KeyManager { /** * Load signing key and certificate - * @param nodeId node ID - * @param keysDir directory path where pem files are stored - * @return returns a dictionary object contains privateKey, certificate, certificateChain + * @param {string} nodeId + * @param {string} keysDir - directory path where pem files are stored + * @returns {Promise} */ async loadSigningKey (nodeId, keysDir) { const nodeKeyFiles = this.prepareNodeKeyFilePaths(nodeId, keysDir, constants.SIGNING_KEY_PREFIX) @@ -339,10 +357,10 @@ export class KeyManager { /** * Generate EC key and cert * - * @param nodeId node ID - * @param keyPrefix key prefix such as constants.PFX_AGREEMENT_KEY_PREFIX - * @param signingKey signing key - * @return a dictionary object stores privateKey, certificate, certificateChain + * @param {string} nodeId + * @param {string} keyPrefix - key prefix such as constants.PFX_AGREEMENT_KEY_PREFIX + * @param {NodeKeyObject} signingKey + * @returns {Promise} a dictionary object stores privateKey, certificate, certificateChain */ async ecKey (nodeId, keyPrefix, signingKey) { if (!nodeId) throw new MissingArgumentError('nodeId is required') @@ -395,9 +413,9 @@ export class KeyManager { /** * Generate agreement key - * @param nodeId node ID - * @param signingKey signing key - * @return a dictionary object stores privateKey, certificate, certificateChain + * @param {string} nodeId + * @param {NodeKeyObject} signingKey + * @returns {Promise} */ async generateAgreementKey (nodeId, signingKey) { return this.ecKey(nodeId, constants.AGREEMENT_KEY_PREFIX, signingKey) @@ -405,10 +423,10 @@ export class KeyManager { /** * Store agreement key and certificate - * @param nodeId node ID - * @param nodeKey an object containing privateKeyPem, certificatePem data - * @param keysDir directory where keys and certs are stored - * @return a Promise that saves the keys and certs as PEM files + * @param {string} nodeId + * @param {NodeKeyObject} nodeKey + * @param {string} keysDir - directory where keys and certs are stored + * @returns {Promise} returns a Promise that saves the keys and certs as PEM files */ async storeAgreementKey (nodeId, nodeKey, keysDir) { const nodeKeyFiles = this.prepareNodeKeyFilePaths(nodeId, keysDir, constants.AGREEMENT_KEY_PREFIX) @@ -417,9 +435,9 @@ export class KeyManager { /** * Load agreement key and certificate - * @param nodeId node ID - * @param keysDir directory path where pem files are stored - * @return a dictionary object contains privateKey, certificate, certificateChain + * @param {string} nodeId + * @param {string} keysDir - directory path where pem files are stored + * @returns {Promise} */ async loadAgreementKey (nodeId, keysDir) { const nodeKeyFiles = this.prepareNodeKeyFilePaths(nodeId, keysDir, constants.AGREEMENT_KEY_PREFIX) @@ -433,9 +451,9 @@ export class KeyManager { * hedera-.key * hedera-.crt * - * @param nodeId - * @param distinguishedName distinguished name as: new x509.Name(`CN=${nodeId},ST=${state},L=${locality},O=${org},OU=${orgUnit},C=${country}`) - * @return {Promise} + * @param {string} nodeId + * @param {x509.Name} distinguishedName distinguished name as: new x509.Name(`CN=${nodeId},ST=${state},L=${locality},O=${org},OU=${orgUnit},C=${country}`) + * @returns {Promise} */ async generateGrpcTLSKey (nodeId, distinguishedName = new x509.Name(`CN=${nodeId}`)) { if (!nodeId) throw new MissingArgumentError('nodeId is required') @@ -483,10 +501,10 @@ export class KeyManager { /** * Store TLS key and certificate - * @param nodeId node ID - * @param nodeKey an object containing privateKeyPem, certificatePem data - * @param keysDir directory where keys and certs are stored - * @return a Promise that saves the keys and certs as PEM files + * @param {string} nodeId + * @param {NodeKeyObject} nodeKey + * @param {string} keysDir - directory where keys and certs are stored + * @returns {Promise} a Promise that saves the keys and certs as PEM files */ async storeTLSKey (nodeId, nodeKey, keysDir) { const nodeKeyFiles = this.prepareTLSKeyFilePaths(nodeId, keysDir) @@ -495,9 +513,9 @@ export class KeyManager { /** * Load TLS key and certificate - * @param nodeId node ID - * @param keysDir directory path where pem files are stored - * @return a dictionary object contains privateKey, certificate, certificateChain + * @param {string} nodeId + * @param {string} keysDir - directory path where pem files are stored + * @returns {Promise} */ async loadTLSKey (nodeId, keysDir) { const nodeKeyFiles = this.prepareTLSKeyFilePaths(nodeId, keysDir) @@ -512,11 +530,11 @@ export class KeyManager { * - a-key & cert: agreement key and signed cert * - e-key & cert: encryption key and signed cert (currently unused) * - * @param keytool an instance of Keytool class - * @param nodeId node id - * @param keysDir directory where the pfx files should be stored - * @param tmpDir tmp directory where intermediate files can be stored. - * @return {Promise} path to the pfx file + * @param {Keytool} keytool - an instance of Keytool class + * @param {string} nodeId + * @param {string} keysDir - directory where the pfx files should be stored + * @param {string} [tmpDir] - tmp directory where intermediate files can be stored. + * @returns {Promise} path to the pfx file */ async generatePrivatePfxKeys (keytool, nodeId, keysDir, tmpDir = getTmpDir()) { if (!keytool || !(keytool instanceof Keytool)) throw new MissingArgumentError('An instance of core/Keytool is required') @@ -611,11 +629,11 @@ export class KeyManager { * * WARNING: do not invoke this method in parallel as the same public.pfx will be modified for the given node ids. * - * @param keytool an instance of core/Keytool - * @param nodeIds node Ids - * @param keysDir keys directory - * @param tmpDir tmp directory where intermediate files can be stored. - * @return {Promise} + * @param {Keytool} keytool - an instance of core/Keytool + * @param {string[]} nodeIds + * @param {string} keysDir - keys directory + * @param {string} [tmpDir] - tmp directory where intermediate files can be stored. + * @returns {Promise} */ async updatePublicPfxKey (keytool, nodeIds, keysDir, tmpDir = getTmpDir()) { if (!keytool || !(keytool instanceof Keytool)) throw new MissingArgumentError('An instance of core/Keytool is required') diff --git a/src/core/keytool.mjs b/src/core/keytool.mjs index 5860c02fd..b5567e639 100644 --- a/src/core/keytool.mjs +++ b/src/core/keytool.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import os from 'os' import { constants } from './index.mjs' import { ShellRunner } from './shell_runner.mjs' @@ -21,6 +22,10 @@ import { Templates } from './templates.mjs' import { MissingArgumentError } from './errors.mjs' export class Keytool extends ShellRunner { + /** + * @param {Logger} logger + * @param {NodeJS.Platform} [osPlatform] + */ constructor (logger, osPlatform = os.platform()) { if (!logger) throw new MissingArgumentError('an instance of core/Logger is required', logger) super(logger) @@ -30,8 +35,8 @@ export class Keytool extends ShellRunner { /** * Prepare a `keytool` shell command string - * @param action represents a helm command (e.g. create | install | get ) - * @param args args of the command + * @param {string} action - represents a helm command (e.g. create | install | get ) + * @param {string} args - args of the command * @returns {string} */ prepareCommand (action, ...args) { @@ -44,7 +49,7 @@ export class Keytool extends ShellRunner { /** * Invoke `keytool -genkeypair` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async genKeyPair (...args) { @@ -53,7 +58,7 @@ export class Keytool extends ShellRunner { /** * Invoke `keytool -certreq` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async certReq (...args) { @@ -62,7 +67,7 @@ export class Keytool extends ShellRunner { /** * Invoke `keytool -gencert` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async genCert (...args) { @@ -71,7 +76,7 @@ export class Keytool extends ShellRunner { /** * Invoke `keytool -importcert` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async importCert (...args) { @@ -80,7 +85,7 @@ export class Keytool extends ShellRunner { /** * Invoke `keytool -exportcert` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async exportCert (...args) { @@ -89,7 +94,7 @@ export class Keytool extends ShellRunner { /** * Invoke `keytool -list` command - * @param args args of the command + * @param {string} args - args of the command * @returns {Promise} console output as an array of strings */ async list (...args) { diff --git a/src/core/logging.mjs b/src/core/logging.mjs index 1337fda89..393d06445 100644 --- a/src/core/logging.mjs +++ b/src/core/logging.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import * as winston from 'winston' import { constants } from './index.mjs' import { v4 as uuidv4 } from 'uuid' @@ -85,11 +86,17 @@ export const Logger = class { }) } + /** + * @param {boolean} devMode + */ setDevMode (devMode) { this.debug(`dev mode logging: ${devMode}`) this.devMode = devMode } + /** + * @param {string} level + */ setLevel (level) { this.winstonLogger.setLevel(level) } @@ -98,6 +105,10 @@ export const Logger = class { this.traceId = uuidv4() } + /** + * @param {Object|undefined} meta + * @returns {Object} + */ prepMeta (meta) { if (meta === undefined) { meta = {} @@ -107,10 +118,17 @@ export const Logger = class { return meta } + /** + * @param msg + * @param args + */ showUser (msg, ...args) { console.log(util.format(msg, ...args)) } + /** + * @param {Error} err + */ showUserError (err) { const stack = [{ message: err.message, stacktrace: err.stack }] if (err.cause) { @@ -147,22 +165,43 @@ export const Logger = class { this.debug(err.message, { error: err.message, stacktrace: stack }) } + /** + * @param {string} msg + * @param {*} args + */ error (msg, ...args) { this.winstonLogger.error(msg, ...args, this.prepMeta()) } + /** + * @param {string} msg + * @param {*} args + */ warn (msg, ...args) { this.winstonLogger.warn(msg, ...args, this.prepMeta()) } + /** + * @param {string} msg + * @param {*} args + */ info (msg, ...args) { this.winstonLogger.info(msg, ...args, this.prepMeta()) } + /** + * @param {string} msg + * @param {*} args + */ debug (msg, ...args) { this.winstonLogger.debug(msg, ...args, this.prepMeta()) } + /** + * @param {string} title + * @param {string[]} items + * @returns {boolean} + */ showList (title, items = []) { this.showUser(chalk.green(`\n *** ${title} ***`)) this.showUser(chalk.green('-------------------------------------------------------------------------------')) @@ -176,6 +215,10 @@ export const Logger = class { return true } + /** + * @param {string} title + * @param {Object} obj + */ showJSON (title, obj) { this.showUser(chalk.green(`\n *** ${title} ***`)) this.showUser(chalk.green('-------------------------------------------------------------------------------')) @@ -183,6 +226,12 @@ export const Logger = class { } } +/** + * @param {string} [level] + * @param {boolean} [devMode] + * @returns {Logger} + * @constructor + */ export function NewLogger (level = 'debug', devMode = false) { return new Logger(level, devMode) } diff --git a/src/core/network_node_services.mjs b/src/core/network_node_services.mjs index 9f2b76a3b..205ec46c5 100644 --- a/src/core/network_node_services.mjs +++ b/src/core/network_node_services.mjs @@ -14,7 +14,31 @@ * limitations under the License. * */ +'use strict' export class NetworkNodeServices { + /** + * @param {Object} builder + * @param {string} builder.nodeName + * @param {string} builder.nodePodName + * @param {string} builder.haProxyName + * @param {string} builder.haProxyLoadBalancerIp + * @param {string} builder.haProxyClusterIp + * @param {string|number} builder.haProxyGrpcPort + * @param {string|number} builder.haProxyGrpcsPort + * @param {string} builder.accountId + * @param {string} builder.haProxyAppSelector + * @param {string} builder.haProxyPodName + * @param {string} builder.nodeServiceName + * @param {string} builder.nodeServiceClusterIp + * @param {string} builder.nodeServiceLoadBalancerIp + * @param {string|number} builder.nodeServiceGossipPort + * @param {string|number} builder.nodeServiceGrpcPort + * @param {string|number} builder.nodeServiceGrpcsPort + * @param {string} builder.envoyProxyName + * @param {string} builder.envoyProxyClusterIp + * @param {string} builder.envoyProxyLoadBalancerIp + * @param {string|number} builder.envoyProxyGrpcWebPort + */ constructor (builder) { this.nodeName = builder.nodeName this.nodePodName = builder.nodePodName @@ -38,115 +62,203 @@ export class NetworkNodeServices { this.envoyProxyGrpcWebPort = builder.envoyProxyGrpcWebPort } + /** + * @returns {string} + */ key () { return this.nodeName } } export class NetworkNodeServicesBuilder { + /** + * @param {string} nodeName + */ constructor (nodeName) { this.nodeName = nodeName } + /** + * @param {string} accountId + * @returns {this} + */ withAccountId (accountId) { this.accountId = accountId return this } + /** + * @param {string} haProxyName + * @returns {this} + */ withHaProxyName (haProxyName) { this.haProxyName = haProxyName return this } + /** + * @param {string} haProxyClusterIp + * @returns {this} + */ withHaProxyClusterIp (haProxyClusterIp) { this.haProxyClusterIp = haProxyClusterIp return this } + /** + * @param {string} haProxyLoadBalancerIp + * @returns {this} + */ withHaProxyLoadBalancerIp (haProxyLoadBalancerIp) { this.haProxyLoadBalancerIp = haProxyLoadBalancerIp return this } + /** + * @param {string|number} haProxyGrpcPort + * @returns {this} + */ withHaProxyGrpcPort (haProxyGrpcPort) { this.haProxyGrpcPort = haProxyGrpcPort return this } + /** + * @param {string|number} haProxyGrpcsPort + * @returns {this} + */ withHaProxyGrpcsPort (haProxyGrpcsPort) { this.haProxyGrpcsPort = haProxyGrpcsPort return this } + /** + * @param {string} haProxyAppSelector + * @returns {this} + */ withHaProxyAppSelector (haProxyAppSelector) { this.haProxyAppSelector = haProxyAppSelector return this } + /** + * @param {string} haProxyPodName + * @returns {this} + */ withHaProxyPodName (haProxyPodName) { this.haProxyPodName = haProxyPodName return this } + /** + * @param {string} nodePodName + * @returns {this} + */ withNodePodName (nodePodName) { this.nodePodName = nodePodName return this } + /** + * @param {string} nodeServiceName + * @returns {this} + */ withNodeServiceName (nodeServiceName) { this.nodeServiceName = nodeServiceName return this } + /** + * @param {string} nodeServiceClusterIp + * @returns {this} + */ withNodeServiceClusterIp (nodeServiceClusterIp) { this.nodeServiceClusterIp = nodeServiceClusterIp return this } + /** + * @param {string} nodeServiceLoadBalancerIp + * @returns {this} + */ withNodeServiceLoadBalancerIp (nodeServiceLoadBalancerIp) { this.nodeServiceLoadBalancerIp = nodeServiceLoadBalancerIp return this } + /** + * @param {string|number} nodeServiceGossipPort + * @returns {this} + */ withNodeServiceGossipPort (nodeServiceGossipPort) { this.nodeServiceGossipPort = nodeServiceGossipPort return this } + /** + * @param {string|number} nodeServiceGrpcPort + * @returns {this} + */ withNodeServiceGrpcPort (nodeServiceGrpcPort) { this.nodeServiceGrpcPort = nodeServiceGrpcPort return this } + /** + * @param {string|number} nodeServiceGrpcsPort + * @returns {this} + */ withNodeServiceGrpcsPort (nodeServiceGrpcsPort) { this.nodeServiceGrpcsPort = nodeServiceGrpcsPort return this } + /** + * @param {string} envoyProxyName + * @returns {this} + */ withEnvoyProxyName (envoyProxyName) { this.envoyProxyName = envoyProxyName return this } + /** + * @param {string} envoyProxyClusterIp + * @returns {this} + */ withEnvoyProxyClusterIp (envoyProxyClusterIp) { this.envoyProxyClusterIp = envoyProxyClusterIp return this } + /** + * @param {string} envoyProxyLoadBalancerIp + * @returns {this} + */ withEnvoyProxyLoadBalancerIp (envoyProxyLoadBalancerIp) { this.envoyProxyLoadBalancerIp = envoyProxyLoadBalancerIp return this } + /** + * @param {string|number} envoyProxyGrpcWebPort + * @returns {this} + */ withEnvoyProxyGrpcWebPort (envoyProxyGrpcWebPort) { this.envoyProxyGrpcWebPort = envoyProxyGrpcWebPort return this } + /** + * @returns {NetworkNodeServices} + */ build () { return new NetworkNodeServices(this) } + /** + * @returns {string} + */ key () { return this.nodeName } diff --git a/src/core/package_downloader.mjs b/src/core/package_downloader.mjs index d8191845c..40c164baa 100644 --- a/src/core/package_downloader.mjs +++ b/src/core/package_downloader.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import * as crypto from 'crypto' import * as fs from 'fs' import { pipeline as streamPipeline } from 'node:stream/promises' @@ -34,13 +35,17 @@ import { constants } from './index.mjs' export class PackageDownloader { /** * Create an instance of Downloader - * @param logger an instance of core/Logger + * @param {Logger} logger - an instance of core/Logger */ constructor (logger) { if (!logger) throw new IllegalArgumentError('an instance of core/Logger is required', logger) this.logger = logger } + /** + * @param {string} url + * @returns {boolean} + */ isValidURL (url) { try { // attempt to parse to check URL format @@ -52,6 +57,10 @@ export class PackageDownloader { return false } + /** + * @param {string} url + * @returns {Promise} + */ async urlExists (url) { const self = this @@ -102,8 +111,9 @@ export class PackageDownloader { /** * Fetch data from a URL and save the output to a file * - * @param url source file URL - * @param destPath destination path for the downloaded file + * @param {string} url - source file URL + * @param {string} destPath - destination path for the downloaded file + * @returns {Promise} */ async fetchFile (url, destPath) { if (!url) { @@ -136,8 +146,8 @@ export class PackageDownloader { /** * Compute hash of the file contents - * @param filePath path of the file - * @param algo hash algorithm + * @param {string} filePath - path of the file + * @param {string} [algo] - hash algorithm * @returns {Promise} returns hex digest of the computed hash * @throws Error if the file cannot be read */ @@ -172,9 +182,10 @@ export class PackageDownloader { * * It throws error if the checksum doesn't match. * - * @param sourceFile path to the file for which checksum to be computed - * @param checksum expected checksum - * @param algo hash algorithm to be used to compute checksum + * @param {string} sourceFile - path to the file for which checksum to be computed + * @param checksum - expected checksum + * @param {string} [algo] - hash algorithm to be used to compute checksum + * @returns {Promise} * @throws DataValidationError if the checksum doesn't match */ async verifyChecksum (sourceFile, checksum, algo = 'sha256') { @@ -184,12 +195,12 @@ export class PackageDownloader { /** * Fetch a remote package - * @param packageURL package URL - * @param checksumURL package checksum URL - * @param destDir a directory where the files should be downloaded to - * @param algo checksum algo - * @param force force download even if the file exists in the destDir - * @return {Promise} + * @param {string} packageURL + * @param {string} checksumURL - package checksum URL + * @param {string} destDir - a directory where the files should be downloaded to + * @param {string} [algo] - checksum algo + * @param {boolean} [force] - force download even if the file exists in the destDir + * @returns {Promise} */ async fetchPackage (packageURL, checksumURL, destDir, algo = 'sha256', force = false) { if (!packageURL) throw new Error('package URL is required') @@ -234,12 +245,11 @@ export class PackageDownloader { * * It fetches the build.zip file containing the release from a URL like: https://builds.hedera.com/node/software/v0.40/build-v0.40.4.zip * - * @param tag full semantic version e.g. v0.40.4 - * @param destDir directory where the artifact needs to be saved - * @param force whether to download even if the file exists + * @param {string} tag - full semantic version e.g. v0.40.4 + * @param {string} destDir - directory where the artifact needs to be saved + * @param {boolean} [force] - whether to download even if the file exists * @returns {Promise} full path to the downloaded file */ - async fetchPlatform (tag, destDir, force = false) { if (!tag) throw new MissingArgumentError('tag is required') if (!destDir) throw new MissingArgumentError('destination directory path is required') diff --git a/src/core/platform_installer.mjs b/src/core/platform_installer.mjs index f6f9a00db..a7b51f9f1 100644 --- a/src/core/platform_installer.mjs +++ b/src/core/platform_installer.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import * as fs from 'fs' import { Listr } from 'listr2' import * as path from 'path' @@ -28,6 +29,12 @@ import chalk from 'chalk' * PlatformInstaller install platform code in the root-container of a network pod */ export class PlatformInstaller { + /** + * @param {Logger} logger + * @param {K8} k8 + * @param {ConfigManager} configManager + * @param {AccountManager} accountManager + */ constructor (logger, k8, configManager, accountManager) { if (!logger) throw new MissingArgumentError('an instance of core/Logger is required') if (!k8) throw new MissingArgumentError('an instance of core/K8 is required') @@ -40,12 +47,20 @@ export class PlatformInstaller { this.accountManager = accountManager } + /** + * @returns {string} + * @private + */ _getNamespace () { const ns = this.configManager.getFlag(flags.namespace) if (!ns) throw new MissingArgumentError('namespace is not set') return ns } + /** + * @param {string} releaseDir + * @returns {Promise} + */ async validatePlatformReleaseDir (releaseDir) { if (!releaseDir) throw new MissingArgumentError('releaseDir is required') if (!fs.existsSync(releaseDir)) { @@ -79,11 +94,10 @@ export class PlatformInstaller { /** * Fetch and extract platform code into the container - * @param podName pod name - * @param tag platform release tag - * @return {Promise} + * @param {string} podName + * @param {string} tag - platform release tag + * @returns {Promise} */ - async fetchPlatform (podName, tag) { if (!podName) throw new MissingArgumentError('podName is required') if (!tag) throw new MissingArgumentError('tag is required') @@ -105,12 +119,11 @@ export class PlatformInstaller { /** * Copy a list of files to a directory in the container * - * @param podName pod name - * @param srcFiles list of source files - * @param destDir destination directory - * @param container name of the container - * - * @return {Promise} list of pathso of the copied files insider the container + * @param {string} podName + * @param {string[]} srcFiles - list of source files + * @param {string} destDir - destination directory + * @param {string} [container] - name of the container + * @returns {Promise} list of pathso of the copied files insider the container */ async copyFiles (podName, srcFiles, destDir, container = constants.ROOT_CONTAINER) { try { @@ -216,6 +229,14 @@ export class PlatformInstaller { } } + /** + * @param {string} podName + * @param {string} destPath + * @param {string} [mode] + * @param {boolean} [recursive] + * @param {string} [container] + * @returns {Promise} + */ async setPathPermission (podName, destPath, mode = '0755', recursive = true, container = constants.ROOT_CONTAINER) { if (!podName) throw new MissingArgumentError('podName is required') if (!destPath) throw new MissingArgumentError('destPath is required') @@ -235,6 +256,10 @@ export class PlatformInstaller { return true } + /** + * @param {string} podName + * @returns {Promise} + */ async setPlatformDirPermissions (podName) { const self = this if (!podName) throw new MissingArgumentError('podName is required') diff --git a/src/core/profile_manager.mjs b/src/core/profile_manager.mjs index 3ab1a0752..7d384fbbf 100644 --- a/src/core/profile_manager.mjs +++ b/src/core/profile_manager.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import fs from 'fs' import path from 'path' import { FullstackTestingError, IllegalArgumentError, MissingArgumentError } from './errors.mjs' @@ -30,10 +31,9 @@ const consensusSidecars = [ export class ProfileManager { /** - * Constructor - * @param logger an instance of core/Logger - * @param configManager an instance of core/ConfigManager - * @param cacheDir cache directory where the values file will be written. A yaml file named .yaml is created. + * @param {Logger} logger - an instance of core/Logger + * @param {ConfigManager} configManager - an instance of core/ConfigManager + * @param {string} cacheDir - cache directory where the values file will be written. A yaml file named .yaml is created. */ constructor (logger, configManager, cacheDir = constants.SOLO_VALUES_DIR) { if (!logger) throw new MissingArgumentError('An instance of core/Logger is required') @@ -41,12 +41,18 @@ export class ProfileManager { this.logger = logger this.configManager = configManager + + /** @type {Map} */ this.profiles = new Map() cacheDir = path.resolve(cacheDir) this.cacheDir = cacheDir } + /** + * @param {boolean} [forceReload] + * @returns {Map} + */ loadProfiles (forceReload = false) { const profileFile = this.configManager.getFlag(flags.profileFile) if (!profileFile) throw new MissingArgumentError('profileFile is required') @@ -74,6 +80,10 @@ export class ProfileManager { return this.profiles } + /** + * @param {string} profileName + * @returns {Object} + */ getProfile (profileName) { if (!profileName) throw new MissingArgumentError('profileName is required') if (!this.profiles || this.profiles.size <= 0) { @@ -86,10 +96,10 @@ export class ProfileManager { /** * Set value in the yaml object - * @param itemPath item path in the yaml - * @param value value to be set - * @param yamlRoot root of the yaml object - * @return {*} + * @param {string} itemPath - item path in the yaml + * @param {*} value - value to be set + * @param {Object} yamlRoot - root of the yaml object + * @returns {Object} * @private */ _setValue (itemPath, value, yamlRoot) { @@ -129,9 +139,9 @@ export class ProfileManager { /** * Set items for the chart - * @param itemPath item path in the yaml, if empty then root of the yaml object will be used - * @param items the element object - * @param yamlRoot root of the yaml object to update + * @param {string} itemPath - item path in the yaml, if empty then root of the yaml object will be used + * @param {*} items - the element object + * @param {Object} yamlRoot - root of the yaml object to update * @private */ _setChartItems (itemPath, items, yamlRoot) { @@ -148,6 +158,12 @@ export class ProfileManager { } } + /** + * @param {Object} profile + * @param {string[]} nodeIds + * @param {Object} yamlRoot + * @returns {Object} + */ resourcesForConsensusPod (profile, nodeIds, yamlRoot) { if (!profile) throw new MissingArgumentError('profile is required') @@ -213,6 +229,11 @@ export class ProfileManager { return yamlRoot } + /** + * @param {Object} profile + * @param {Object} yamlRoot + * @returns {void} + */ resourcesForHaProxyPod (profile, yamlRoot) { if (!profile) throw new MissingArgumentError('profile is required') if (!profile.haproxy) return // use chart defaults @@ -220,18 +241,33 @@ export class ProfileManager { return this._setChartItems('defaults.haproxy', profile.haproxy, yamlRoot) } + /** + * @param {Object} profile + * @param {Object} yamlRoot + * @returns {void} + */ resourcesForEnvoyProxyPod (profile, yamlRoot) { if (!profile) throw new MissingArgumentError('profile is required') if (!profile.envoyProxy) return // use chart defaults return this._setChartItems('defaults.envoyProxy', profile.envoyProxy, yamlRoot) } + /** + * @param {Object} profile + * @param {Object} yamlRoot + * @returns {void} + */ resourcesForHederaExplorerPod (profile, yamlRoot) { if (!profile) throw new MissingArgumentError('profile is required') if (!profile.explorer) return return this._setChartItems('hedera-explorer', profile.explorer, yamlRoot) } + /** + * @param {Object} profile + * @param {Object} yamlRoot + * @returns {Object} + */ resourcesForMinioTenantPod (profile, yamlRoot) { if (!profile) throw new MissingArgumentError('profile is required') if (!profile.minio || !profile.minio.tenant) return // use chart defaults @@ -253,7 +289,7 @@ export class ProfileManager { /** * Prepare a values file for FST Helm chart * @param {string} profileName resource profile name - * @return {Promise} return the full path to the values file + * @returns {Promise} return the full path to the values file */ prepareValuesForFstChart (profileName) { if (!profileName) throw new MissingArgumentError('profileName is required') @@ -282,6 +318,10 @@ export class ProfileManager { }) } + /** + * @param {PathLike|FileHandle} applicationPropertiesPath + * @returns {Promise} + */ async bumpHederaConfigVersion (applicationPropertiesPath) { const lines = (await readFile(applicationPropertiesPath, 'utf-8')).split('\n') @@ -296,6 +336,11 @@ export class ProfileManager { await writeFile(applicationPropertiesPath, lines.join('\n')) } + /** + * @param {string} configTxtPath + * @param {string} applicationPropertiesPath + * @returns {Promise} + */ async prepareValuesForNodeAdd (configTxtPath, applicationPropertiesPath) { const yamlRoot = {} this._setFileContentsAsValue('hedera.configMaps.configTxt', configTxtPath, yamlRoot) @@ -317,8 +362,8 @@ export class ProfileManager { /** * Prepare a values file for rpc-relay Helm chart - * @param profileName resource profile name - * @return {Promise} return the full path to the values file + * @param {string} profileName - resource profile name + * @returns {Promise} return the full path to the values file */ prepareValuesForRpcRelayChart (profileName) { if (!profileName) throw new MissingArgumentError('profileName is required') @@ -344,8 +389,8 @@ export class ProfileManager { /** * Prepare a values file for mirror-node Helm chart - * @param profileName resource profile name - * @return {Promise} return the full path to the values file + * @param {string} profileName - resource profile name + * @returns {Promise} return the full path to the values file */ prepareValuesForMirrorNodeChart (profileName) { if (!profileName) throw new MissingArgumentError('profileName is required') @@ -384,9 +429,9 @@ export class ProfileManager { /** * Writes the contents of a file as a value for the given nested item path in the yaml object - * @param {string} itemPath nested item path in the yaml object to store the file contents - * @param {string} valueFilePath path to the file whose contents will be stored in the yaml object - * @param {Object} yamlRoot root of the yaml object + * @param {string} itemPath - nested item path in the yaml object to store the file contents + * @param {string} valueFilePath - path to the file whose contents will be stored in the yaml object + * @param {Object} yamlRoot - root of the yaml object * @private */ _setFileContentsAsValue (itemPath, valueFilePath, yamlRoot) { @@ -396,13 +441,13 @@ export class ProfileManager { /** * Prepares config.txt file for the node - * @param {string} namespace namespace where the network is deployed - * @param {Map} nodeAccountMap the map of node IDs to account IDs - * @param {string} destPath path to the destination directory to write the config.txt file - * @param {string} releaseTag release tag e.g. v0.42.0 - * @param {string} appName the app name (default: HederaNode.jar) - * @param {string} chainId chain ID (298 for local network) - * @param {string} template path to the config.template file + * @param {string} namespace - namespace where the network is deployed + * @param {Map} nodeAccountMap - the map of node IDs to account IDs + * @param {string} destPath - path to the destination directory to write the config.txt file + * @param {string} releaseTag - release tag e.g. v0.42.0 + * @param {string} [appName] - the app name (default: HederaNode.jar) + * @param {string} [chainId] - chain ID (298 for local network) + * @param {string} [template] - path to the config.template file * @returns {string} the config.txt file path */ prepareConfigTxt (namespace, nodeAccountMap, destPath, releaseTag, appName = constants.HEDERA_APP_NAME, chainId = constants.HEDERA_CHAIN_ID, template = path.join(constants.RESOURCES_DIR, 'templates', 'config.template')) { diff --git a/src/core/shell_runner.mjs b/src/core/shell_runner.mjs index aac204b88..fe545d7b7 100644 --- a/src/core/shell_runner.mjs +++ b/src/core/shell_runner.mjs @@ -14,10 +14,14 @@ * limitations under the License. * */ +'use strict' import { spawn } from 'child_process' import chalk from 'chalk' export class ShellRunner { + /** + * @param {Logger} logger + */ constructor (logger) { if (!logger) throw new Error('An instance of core/Logger is required') this.logger = logger @@ -25,9 +29,9 @@ export class ShellRunner { /** * Returns a promise that invokes the shell command - * @param {string} cmd shell command string + * @param {string} cmd - shell command string * @param {boolean} verbose - if true, the output will be shown in the console - * @returns {Promise} console output as an array of strings + * @returns {Promise} console output as an array of strings */ async run (cmd, verbose = false) { const self = this diff --git a/src/core/templates.mjs b/src/core/templates.mjs index 079d49e96..ea6f38467 100644 --- a/src/core/templates.mjs +++ b/src/core/templates.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import * as x509 from '@peculiar/x509' import os from 'os' import path from 'path' @@ -21,62 +22,110 @@ import { DataValidationError, FullstackTestingError, IllegalArgumentError, Missi import { constants } from './index.mjs' export class Templates { + /** + * @param {string} nodeId + * @returns {string} + */ static renderNetworkPodName (nodeId) { return `network-${nodeId}-0` } + /** + * @param {string} nodeId + * @returns {string} + */ static renderNetworkSvcName (nodeId) { return `network-${nodeId}-svc` } + /** + * @param {string} svcName + * @returns {string} + */ static nodeIdFromNetworkSvcName (svcName) { return svcName.split('-').slice(1, -1).join('-') } + /** + * @param {string} nodeId + * @returns {string} + */ static renderNetworkHeadlessSvcName (nodeId) { return `network-${nodeId}` } /** * Generate pfx node private key file name - * @param nodeId node ID + * @param {string} nodeId * @returns {string} */ static renderGossipPfxPrivateKeyFile (nodeId) { return `private-${nodeId}.pfx` } + /** + * @param {string} prefix + * @param {string} nodeId + * @returns {string} + */ static renderGossipPemPrivateKeyFile (prefix, nodeId) { // s-node0-key.pem return `${prefix}-private-${nodeId}.pem` } + /** + * @param {string} prefix + * @param {string} nodeId + * @returns {string} + */ static renderGossipPemPublicKeyFile (prefix, nodeId) { // s-node0-cert.pem return `${prefix}-public-${nodeId}.pem` } + /** + * @param {string} nodeId + * @returns {string} + */ static renderTLSPemPrivateKeyFile (nodeId) { return `hedera-${nodeId}.key` } + /** + * @param {string} nodeId + * @returns {string} + */ static renderTLSPemPublicKeyFile (nodeId) { // s-node0-cert.pem return `hedera-${nodeId}.crt` } + /** + * @param {string} prefix + * @param {string} nodeId + * @param {string} [suffix] + * @returns {string} + */ static renderNodeFriendlyName (prefix, nodeId, suffix = '') { const parts = [prefix, nodeId] if (suffix) parts.push(suffix) return parts.join('-') } + /** + * @param {string} podName + * @returns {string} + */ static extractNodeIdFromPodName (podName) { const parts = podName.split('-') if (parts.length !== 3) throw new DataValidationError(`pod name is malformed : ${podName}`, 3, parts.length) return parts[1].trim() } + /** + * @param {string} tag + * @returns {string} + */ static prepareReleasePrefix (tag) { if (!tag) throw new MissingArgumentError('tag cannot be empty') @@ -87,7 +136,7 @@ export class Templates { /** * renders the name to be used to store the new account key as a Kubernetes secret - * @param accountId the account ID, string or AccountId type + * @param {AccountId|string} accountId * @returns {string} the name of the Kubernetes secret to store the account key */ static renderAccountKeySecretName (accountId) { @@ -96,7 +145,7 @@ export class Templates { /** * renders the label selector to be used to fetch the new account key from the Kubernetes secret - * @param accountId the account ID, string or AccountId type + * @param {AccountId|string} accountId * @returns {string} the label selector of the Kubernetes secret to retrieve the account key */ static renderAccountKeySecretLabelSelector (accountId) { return `fullstack.hedera.com/account-id=${accountId.toString()}` @@ -104,7 +153,7 @@ export class Templates { /** * renders the label object to be used to store the new account key in the Kubernetes secret - * @param accountId the account ID, string or AccountId type + * @param {AccountId|string} accountId * @returns {{'fullstack.hedera.com/account-id': string}} the label object to be used to * store the new account key in the Kubernetes secret */ @@ -114,6 +163,15 @@ export class Templates { } } + /** + * @param {string} nodeId + * @param {string} [state] + * @param {string} [locality] + * @param {string} [org] + * @param {string} [orgUnit] + * @param {string} [country] + * @returns {x509.Name} + */ static renderDistinguishedName (nodeId, state = 'TX', locality = 'Richardson', @@ -124,6 +182,11 @@ export class Templates { return new x509.Name(`CN=${nodeId},ST=${state},L=${locality},O=${org},OU=${orgUnit},C=${country}`) } + /** + * @param {string} cacheDir + * @param {string} releaseTag + * @returns {string} + */ static renderStagingDir (cacheDir, releaseTag) { if (!cacheDir) { throw new IllegalArgumentError('cacheDir cannot be empty') @@ -141,6 +204,12 @@ export class Templates { return path.resolve(path.join(cacheDir, releasePrefix, 'staging', releaseTag)) } + /** + * @param {string} dep + * @param {NodeJS.Platform} [osPlatform] + * @param {string} [installationDir] + * @returns {string} + */ static installationPath ( dep, osPlatform = os.platform(), @@ -165,19 +234,37 @@ export class Templates { } } + /** + * @param {string} namespace + * @param {string} nodeId + * @returns {string} + */ static renderFullyQualifiedNetworkPodName (namespace, nodeId) { return `${Templates.renderNetworkPodName(nodeId)}.${Templates.renderNetworkHeadlessSvcName(nodeId)}.${namespace}.svc.cluster.local` } + /** + * @param {string} namespace + * @param {string} nodeId + * @returns {string} + */ static renderFullyQualifiedNetworkSvcName (namespace, nodeId) { return `${Templates.renderNetworkSvcName(nodeId)}.${namespace}.svc.cluster.local` } + /** + * @param {string} svcName + * @returns {string} + */ static nodeIdFromFullyQualifiedNetworkSvcName (svcName) { const parts = svcName.split('.') return this.nodeIdFromNetworkSvcName(parts[0]) } + /** + * @param {string} nodeId + * @returns {number} + */ static nodeNumberFromNodeId (nodeId) { for (let i = nodeId.length - 1; i > 0; i--) { if (isNaN(nodeId[i])) { diff --git a/src/core/zippy.mjs b/src/core/zippy.mjs index 0c362405b..512d9bb08 100644 --- a/src/core/zippy.mjs +++ b/src/core/zippy.mjs @@ -14,6 +14,7 @@ * limitations under the License. * */ +'use strict' import { FullstackTestingError, IllegalArgumentError, MissingArgumentError } from './errors.mjs' import fs from 'fs' import AdmZip from 'adm-zip' @@ -22,6 +23,9 @@ import chalk from 'chalk' import path from 'path' export class Zippy { + /** + * @param {Logger} logger + */ constructor (logger) { if (!logger) throw new Error('An instance of core/Logger is required') this.logger = logger @@ -29,9 +33,9 @@ export class Zippy { /** * Zip a file or directory - * @param {string} srcPath path to a file or directory - * @param {string} destPath path to the output zip file - * @param {boolean} verbose if true, log the progress + * @param {string} srcPath - path to a file or directory + * @param {string} destPath - path to the output zip file + * @param {boolean} [verbose] - if true, log the progress * @returns {Promise} path to the output zip file */ async zip (srcPath, destPath, verbose = false) { @@ -57,6 +61,12 @@ export class Zippy { } } + /** + * @param {string} srcPath + * @param {string} destPath + * @param {boolean} [verbose] + * @returns {Promise} + */ async unzip (srcPath, destPath, verbose = false) { const self = this @@ -88,6 +98,11 @@ export class Zippy { } } + /** + * @param {string} srcPath + * @param {string} destPath + * @returns {Promise} + */ async tar (srcPath, destPath) { if (!srcPath) throw new MissingArgumentError('srcPath is required') if (!destPath) throw new MissingArgumentError('destPath is required') @@ -107,6 +122,11 @@ export class Zippy { } } + /** + * @param {string} srcPath + * @param {string} destPath + * @returns {Promise} + */ async untar (srcPath, destPath) { if (!srcPath) throw new MissingArgumentError('srcPath is required') if (!destPath) throw new MissingArgumentError('destPath is required')