diff --git a/src/commands/base.ts b/src/commands/base.ts index 1a8e8051a..f7939da25 100644 --- a/src/commands/base.ts +++ b/src/commands/base.ts @@ -2,7 +2,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import paths from 'path'; import {MissingArgumentError, SoloError} from '../core/errors.js'; import {ShellRunner} from '../core/shell_runner.js'; import {type LeaseManager} from '../core/lease/lease_manager.js'; @@ -21,10 +20,7 @@ import path from 'path'; import * as constants from '../core/constants.js'; import fs from 'fs'; import {Task} from '../core/task.js'; - -export interface CommandHandlers { - parent: BaseCommand; -} +import {getConfig} from '../core/config_builder.js'; export abstract class BaseCommand extends ShellRunner { protected readonly helm: Helm; @@ -58,32 +54,6 @@ export abstract class BaseCommand extends ShellRunner { this.remoteConfigManager = opts.remoteConfigManager; } - async prepareChartPath(chartDir: string, chartRepo: string, chartReleaseName: string) { - if (!chartRepo) throw new MissingArgumentError('chart repo name is required'); - if (!chartReleaseName) throw new MissingArgumentError('chart release name is required'); - - if (chartDir) { - const chartPath = path.join(chartDir, chartReleaseName); - await this.helm.dependency('update', chartPath); - return chartPath; - } - - return `${chartRepo}/${chartReleaseName}`; - } - - prepareValuesFiles(valuesFile: string) { - let valuesArg = ''; - if (valuesFile) { - const valuesFiles = valuesFile.split(','); - valuesFiles.forEach(vf => { - const vfp = paths.resolve(vf); - valuesArg += ` --values ${vfp}`; - }); - } - - return valuesArg; - } - getConfigManager(): ConfigManager { return this.configManager; } @@ -98,71 +68,7 @@ export abstract class BaseCommand extends ShellRunner { * getUnusedConfigs() to get an array of unused properties. */ getConfig(configName: string, flags: CommandFlag[], extraProperties: string[] = []): object { - const configManager = this.configManager; - - // build the dynamic class that will keep track of which properties are used - const NewConfigClass = class { - private usedConfigs: Map; - constructor() { - // the map to keep track of which properties are used - this.usedConfigs = new Map(); - - // add the flags as properties to this class - flags?.forEach(flag => { - // @ts-ignore - this[`_${flag.constName}`] = configManager.getFlag(flag); - Object.defineProperty(this, flag.constName, { - get() { - this.usedConfigs.set(flag.constName, this.usedConfigs.get(flag.constName) + 1 || 1); - return this[`_${flag.constName}`]; - }, - }); - }); - - // add the extra properties as properties to this class - extraProperties?.forEach(name => { - // @ts-ignore - this[`_${name}`] = ''; - Object.defineProperty(this, name, { - get() { - this.usedConfigs.set(name, this.usedConfigs.get(name) + 1 || 1); - return this[`_${name}`]; - }, - set(value) { - this[`_${name}`] = value; - }, - }); - }); - } - - /** Get the list of unused configurations that were not accessed */ - getUnusedConfigs() { - const unusedConfigs: string[] = []; - - // add the flag constName to the unusedConfigs array if it was not accessed - flags?.forEach(flag => { - if (!this.usedConfigs.has(flag.constName)) { - unusedConfigs.push(flag.constName); - } - }); - - // add the extra properties to the unusedConfigs array if it was not accessed - extraProperties?.forEach(item => { - if (!this.usedConfigs.has(item)) { - unusedConfigs.push(item); - } - }); - return unusedConfigs; - } - }; - - const newConfigInstance = new NewConfigClass(); - - // add the new instance to the configMaps so that it can be used to get the - // unused configurations using the configName from the BaseCommand - this._configMaps.set(configName, newConfigInstance); - - return newConfigInstance; + return getConfig(this.configManager, this._configMaps, configName, flags, extraProperties); } getLeaseManager(): LeaseManager { @@ -191,38 +97,18 @@ export abstract class BaseCommand extends ShellRunner { abstract close(): Promise; - commandActionBuilder(actionTasks: any, options: any, errorString: string, lease: Lease | null) { - return async function (argv: any, commandDef: CommandHandlers) { - const tasks = new Listr([...actionTasks], options); - - try { - await tasks.run(); - } catch (e: Error | any) { - commandDef.parent.logger.error(`${errorString}: ${e.message}`, e); - throw new SoloError(`${errorString}: ${e.message}`, e); - } finally { - const promises = []; - - promises.push(commandDef.parent.close()); - - if (lease) promises.push(lease.release()); - await Promise.all(promises); - } - }; - } - /** * Setup home directories * @param dirs a list of directories that need to be created in sequence */ - setupHomeDirectory( + public setupHomeDirectory( dirs: string[] = [ constants.SOLO_HOME_DIR, constants.SOLO_LOGS_DIR, constants.SOLO_CACHE_DIR, constants.SOLO_VALUES_DIR, ], - ) { + ): string[] { const self = this; try { @@ -233,14 +119,14 @@ export abstract class BaseCommand extends ShellRunner { self.logger.debug(`OK: setup directory: ${dirPath}`); }); } catch (e: Error | any) { - this.logger.error(e); + self.logger.error(e); throw new SoloError(`failed to create directory: ${e.message}`, e); } return dirs; } - setupHomeDirectoryTask() { + public setupHomeDirectoryTask(): Task { return new Task('Setup home directory', async () => { this.setupHomeDirectory(); }); diff --git a/src/commands/cluster/configs.ts b/src/commands/cluster/configs.ts index caf24779c..0b2f0b261 100644 --- a/src/commands/cluster/configs.ts +++ b/src/commands/cluster/configs.ts @@ -23,8 +23,7 @@ export const connectConfigBuilder = async function (argv, ctx, task) { }; export const setupConfigBuilder = async function (argv, ctx, task) { - const parent = this.parent; - const configManager = parent.getConfigManager(); + const configManager = this.configManager; configManager.update(argv); flags.disablePrompts([flags.chartDirectory]); @@ -47,11 +46,12 @@ export const setupConfigBuilder = async function (argv, ctx, task) { soloChartVersion: configManager.getFlag(flags.soloChartVersion) as string, } as ClusterSetupConfigClass; - parent.logger.debug('Prepare ctx.config', {config: ctx.config, argv}); + this.logger.debug('Prepare ctx.config', {config: ctx.config, argv}); - ctx.isChartInstalled = await parent - .getChartManager() - .isChartInstalled(ctx.config.clusterSetupNamespace, constants.SOLO_CLUSTER_SETUP_CHART); + ctx.isChartInstalled = await this.chartManager.isChartInstalled( + ctx.config.clusterSetupNamespace, + constants.SOLO_CLUSTER_SETUP_CHART, + ); return ctx.config; }; @@ -70,16 +70,17 @@ export const resetConfigBuilder = async function (argv, ctx, task) { } } - this.parent.getConfigManager().update(argv); + this.configManager.update(argv); ctx.config = { - clusterName: this.parent.getConfigManager().getFlag(flags.clusterName) as string, - clusterSetupNamespace: this.parent.getConfigManager().getFlag(flags.clusterSetupNamespace) as string, + clusterName: this.configManager.getFlag(flags.clusterName) as string, + clusterSetupNamespace: this.configManager.getFlag(flags.clusterSetupNamespace) as string, } as ClusterResetConfigClass; - ctx.isChartInstalled = await this.parent - .getChartManager() - .isChartInstalled(ctx.config.clusterSetupNamespace, constants.SOLO_CLUSTER_SETUP_CHART); + ctx.isChartInstalled = await this.chartManager.isChartInstalled( + ctx.config.clusterSetupNamespace, + constants.SOLO_CLUSTER_SETUP_CHART, + ); if (!ctx.isChartInstalled) { throw new SoloError('No chart found for the cluster'); } diff --git a/src/commands/cluster/handlers.ts b/src/commands/cluster/handlers.ts index 8ef6fb9fd..f590c8270 100644 --- a/src/commands/cluster/handlers.ts +++ b/src/commands/cluster/handlers.ts @@ -1,39 +1,50 @@ /** * SPDX-License-Identifier: Apache-2.0 */ -import {type BaseCommand, type CommandHandlers} from '../base.js'; -import {type ClusterCommandTasks} from './tasks.js'; +import {ClusterCommandTasks} from './tasks.js'; import * as helpers from '../../core/helpers.js'; import * as constants from '../../core/constants.js'; import * as ContextFlags from './flags.js'; import {ListrRemoteConfig} from '../../core/config/remote/listr_config_tasks.js'; -import {type RemoteConfigManager} from '../../core/config/remote/remote_config_manager.js'; +import {RemoteConfigManager} from '../../core/config/remote/remote_config_manager.js'; import {connectConfigBuilder, resetConfigBuilder, setupConfigBuilder} from './configs.js'; import {SoloError} from '../../core/errors.js'; - -export class ClusterCommandHandlers implements CommandHandlers { - readonly parent: BaseCommand; - readonly tasks: ClusterCommandTasks; - public readonly remoteConfigManager: RemoteConfigManager; - private getConfig: any; - - constructor(parent: BaseCommand, tasks: ClusterCommandTasks, remoteConfigManager: RemoteConfigManager) { - this.parent = parent; - this.tasks = tasks; - this.remoteConfigManager = remoteConfigManager; - this.getConfig = parent.getConfig.bind(parent); +import {inject, injectable} from 'tsyringe-neo'; +import {patchInject} from '../../core/container_helper.js'; +import {K8Client} from '../../core/kube/k8_client.js'; +import {type K8} from '../../core/kube/k8.js'; +import {CommandHandler} from '../../core/command_handler.js'; +import {LocalConfig} from '../../core/config/local_config.js'; +import {ChartManager} from '../../core/chart_manager.js'; + +@injectable() +export class ClusterCommandHandlers extends CommandHandler { + constructor( + @inject(ClusterCommandTasks) private readonly tasks: ClusterCommandTasks, + @inject(RemoteConfigManager) private readonly remoteConfigManager: RemoteConfigManager, + @inject(LocalConfig) private readonly localConfig: LocalConfig, + @inject(K8Client) private readonly k8: K8, + @inject(ChartManager) private readonly chartManager: ChartManager, + ) { + super(); + + this.tasks = patchInject(tasks, ClusterCommandTasks, this.constructor.name); + this.remoteConfigManager = patchInject(remoteConfigManager, RemoteConfigManager, this.constructor.name); + this.k8 = patchInject(k8, K8Client, this.constructor.name); + this.localConfig = patchInject(localConfig, LocalConfig, this.constructor.name); + this.chartManager = patchInject(chartManager, ChartManager, this.constructor.name); } async connect(argv: any) { argv = helpers.addFlagsToArgv(argv, ContextFlags.USE_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, connectConfigBuilder.bind(this)), - this.parent.setupHomeDirectoryTask(), - this.parent.getLocalConfig().promptLocalConfigTask(this.parent.getK8()), + this.setupHomeDirectoryTask(), + this.localConfig.promptLocalConfigTask(this.k8), this.tasks.selectContext(), - ListrRemoteConfig.loadRemoteConfig(this.parent, argv), + ListrRemoteConfig.loadRemoteConfig(this.remoteConfigManager, argv), this.tasks.readClustersFromRemoteConfig(argv), this.tasks.updateLocalConfig(), ], @@ -52,7 +63,7 @@ export class ClusterCommandHandlers implements CommandHandlers { async list(argv: any) { argv = helpers.addFlagsToArgv(argv, ContextFlags.USE_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [this.tasks.showClusterList()], { concurrent: false, @@ -69,7 +80,7 @@ export class ClusterCommandHandlers implements CommandHandlers { async info(argv: any) { argv = helpers.addFlagsToArgv(argv, ContextFlags.USE_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [this.tasks.getClusterInfo()], { concurrent: false, @@ -86,7 +97,7 @@ export class ClusterCommandHandlers implements CommandHandlers { async setup(argv: any) { argv = helpers.addFlagsToArgv(argv, ContextFlags.USE_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, setupConfigBuilder.bind(this)), this.tasks.prepareChartValues(argv), @@ -112,7 +123,7 @@ export class ClusterCommandHandlers implements CommandHandlers { async reset(argv: any) { argv = helpers.addFlagsToArgv(argv, ContextFlags.USE_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, resetConfigBuilder.bind(this)), this.tasks.acquireNewLease(argv), diff --git a/src/commands/cluster/index.ts b/src/commands/cluster/index.ts index 352aabbe9..cc6124d29 100644 --- a/src/commands/cluster/index.ts +++ b/src/commands/cluster/index.ts @@ -6,9 +6,9 @@ import * as ContextFlags from './flags.js'; import {YargsCommand} from '../../core/yargs_command.js'; import {BaseCommand} from './../base.js'; import {type Opts} from '../../types/command_types.js'; -import {ClusterCommandTasks} from './tasks.js'; import {ClusterCommandHandlers} from './handlers.js'; import {DEFAULT_FLAGS, RESET_FLAGS, SETUP_FLAGS} from './flags.js'; +import {patchInject} from '../../core/container_helper.js'; /** * Defines the core functionalities of 'node' command @@ -19,7 +19,7 @@ export class ClusterCommand extends BaseCommand { constructor(opts: Opts) { super(opts); - this.handlers = new ClusterCommandHandlers(this, new ClusterCommandTasks(this, this.k8), this.remoteConfigManager); + this.handlers = patchInject(null, ClusterCommandHandlers, this.constructor.name); } getCommandDefinition() { diff --git a/src/commands/cluster/tasks.ts b/src/commands/cluster/tasks.ts index fc6220862..7b97dae5c 100644 --- a/src/commands/cluster/tasks.ts +++ b/src/commands/cluster/tasks.ts @@ -6,7 +6,7 @@ import {Flags as flags} from '../flags.js'; import {type ListrTaskWrapper} from 'listr2'; import {type ConfigBuilder} from '../../types/aliases.js'; import {type BaseCommand} from '../base.js'; -import {splitFlagInput} from '../../core/helpers.js'; +import {prepareChartPath, splitFlagInput} from '../../core/helpers.js'; import * as constants from '../../core/constants.js'; import path from 'path'; import chalk from 'chalk'; @@ -16,43 +16,69 @@ import {SoloError} from '../../core/errors.js'; import {RemoteConfigManager} from '../../core/config/remote/remote_config_manager.js'; import {type RemoteConfigDataWrapper} from '../../core/config/remote/remote_config_data_wrapper.js'; import {type K8} from '../../core/kube/k8.js'; +import {K8Client} from '../../core/kube/k8_client.js'; import {type SoloListrTask, type SoloListrTaskWrapper} from '../../types/index.js'; import {type SelectClusterContextContext} from './configs.js'; import {type DeploymentName} from '../../core/config/remote/types.js'; -import {type LocalConfig} from '../../core/config/local_config.js'; +import {LocalConfig} from '../../core/config/local_config.js'; import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; +import {inject, injectable} from 'tsyringe-neo'; +import {patchInject} from '../../core/container_helper.js'; +import {ConfigManager} from '../../core/config_manager.js'; +import {SoloLogger} from '../../core/logging.js'; +import {ChartManager} from '../../core/chart_manager.js'; +import {LeaseManager} from '../../core/lease/lease_manager.js'; +import {Helm} from '../../core/helm.js'; import {type NamespaceName} from '../../core/kube/namespace_name.js'; import {ClusterChecks} from '../../core/cluster_checks.js'; import {container} from 'tsyringe-neo'; +@injectable() export class ClusterCommandTasks { private readonly parent: BaseCommand; private readonly clusterChecks: ClusterChecks = container.resolve(ClusterChecks); constructor( - parent, - private readonly k8: K8, + @inject(K8Client) private readonly k8: K8, + @inject(ConfigManager) private readonly configManager: ConfigManager, + @inject(RemoteConfigManager) private readonly remoteConfigManager: RemoteConfigManager, + @inject(LocalConfig) private readonly localConfig: LocalConfig, + @inject(SoloLogger) private readonly logger: SoloLogger, + @inject(ChartManager) private readonly chartManager: ChartManager, + @inject(LeaseManager) private readonly leaseManager: LeaseManager, + @inject(Helm) private readonly helm: Helm, ) { - this.parent = parent; + this.k8 = patchInject(k8, K8Client, this.constructor.name); + this.configManager = patchInject(configManager, ConfigManager, this.constructor.name); + this.remoteConfigManager = patchInject(remoteConfigManager, RemoteConfigManager, this.constructor.name); + this.localConfig = patchInject(localConfig, LocalConfig, this.constructor.name); + this.logger = patchInject(logger, SoloLogger, this.constructor.name); + this.chartManager = patchInject(chartManager, ChartManager, this.constructor.name); + this.leaseManager = patchInject(leaseManager, LeaseManager, this.constructor.name); + this.helm = patchInject(helm, Helm, this.constructor.name); } - testConnectionToCluster(cluster: string, localConfig: LocalConfig, parentTask: ListrTaskWrapper) { + public testConnectionToCluster( + cluster: string, + localConfig: LocalConfig, + parentTask: ListrTaskWrapper, + ) { const self = this; return { title: `Test connection to cluster: ${chalk.cyan(cluster)}`, task: async (_, subTask: ListrTaskWrapper) => { let context = localConfig.clusterContextMapping[cluster]; if (!context) { - const isQuiet = self.parent.getConfigManager().getFlag(flags.quiet); + const isQuiet = self.configManager.getFlag(flags.quiet); if (isQuiet) { - context = self.parent.getK8().getCurrentContext(); + context = self.k8.getCurrentContext(); } else { context = await self.promptForContext(parentTask, cluster); } localConfig.clusterContextMapping[cluster] = context; } - if (!(await self.parent.getK8().testContextConnection(context))) { + if (!(await self.k8.testContextConnection(context))) { subTask.title = `${subTask.title} - ${chalk.red('Cluster connection failed')}`; throw new SoloError(`${ErrorMessages.INVALID_CONTEXT_FOR_CLUSTER_DETAILED(context, cluster)}`); } @@ -60,7 +86,7 @@ export class ClusterCommandTasks { }; } - validateRemoteConfigForCluster( + public validateRemoteConfigForCluster( cluster: string, currentClusterName: string, localConfig: LocalConfig, @@ -71,8 +97,8 @@ export class ClusterCommandTasks { title: `Pull and validate remote configuration for cluster: ${chalk.cyan(cluster)}`, task: async (_, subTask: ListrTaskWrapper) => { const context = localConfig.clusterContextMapping[cluster]; - self.parent.getK8().setCurrentContext(context); - const remoteConfigFromOtherCluster = await self.parent.getRemoteConfigManager().get(); + self.k8.setCurrentContext(context); + const remoteConfigFromOtherCluster = await self.remoteConfigManager.get(); if (!RemoteConfigManager.compare(currentRemoteConfig, remoteConfigFromOtherCluster)) { throw new SoloError(ErrorMessages.REMOTE_CONFIGS_DO_NOT_MATCH(currentClusterName, cluster)); } @@ -80,14 +106,14 @@ export class ClusterCommandTasks { }; } - readClustersFromRemoteConfig(argv) { + public readClustersFromRemoteConfig(argv) { const self = this; return { title: 'Read clusters from remote config', task: async (ctx, task) => { - const localConfig = this.parent.getLocalConfig(); - const currentClusterName = this.parent.getK8().getCurrentClusterName(); - const currentRemoteConfig: RemoteConfigDataWrapper = await this.parent.getRemoteConfigManager().get(); + const localConfig = this.localConfig; + const currentClusterName = this.k8.getCurrentClusterName(); + const currentRemoteConfig: RemoteConfigDataWrapper = await this.remoteConfigManager.get(); const subTasks = []; const remoteConfigClusters = Object.keys(currentRemoteConfig.clusters); const otherRemoteConfigClusters: string[] = remoteConfigClusters.filter(c => c !== currentClusterName); @@ -112,15 +138,15 @@ export class ClusterCommandTasks { }; } - updateLocalConfig(): SoloListrTask { + public updateLocalConfig(): SoloListrTask { return new Task('Update local configuration', async (ctx: any, task: ListrTaskWrapper) => { - this.parent.logger.info('Compare local and remote configuration...'); - const configManager = this.parent.getConfigManager(); + this.logger.info('Compare local and remote configuration...'); + const configManager = this.configManager; const isQuiet = configManager.getFlag(flags.quiet); - await this.parent.getRemoteConfigManager().modify(async remoteConfig => { + await this.remoteConfigManager.modify(async remoteConfig => { // Update current deployment with cluster list from remoteConfig - const localConfig = this.parent.getLocalConfig(); + const localConfig = this.localConfig; const localDeployments = localConfig.deployments; const remoteClusterList: string[] = []; @@ -155,7 +181,7 @@ export class ClusterCommandTasks { } else if (!localConfig.clusterContextMapping[cluster]) { // In quiet mode, use the currently selected context to update the mapping if (isQuiet) { - localConfig.clusterContextMapping[cluster] = this.parent.getK8().getCurrentContext(); + localConfig.clusterContextMapping[cluster] = this.k8.getCurrentContext(); } // Prompt the user to select a context if mapping value is missing @@ -164,7 +190,7 @@ export class ClusterCommandTasks { } } } - this.parent.logger.info('Update local configuration...'); + this.logger.info('Update local configuration...'); await localConfig.write(); }); }); @@ -178,7 +204,7 @@ export class ClusterCommandTasks { ) { let selectedContext; if (isQuiet) { - selectedContext = this.parent.getK8().getCurrentContext(); + selectedContext = this.k8.getCurrentContext(); } else { selectedContext = await this.promptForContext(task, selectedCluster); localConfig.clusterContextMapping[selectedCluster] = selectedContext; @@ -187,7 +213,7 @@ export class ClusterCommandTasks { } private async promptForContext(task: SoloListrTaskWrapper, cluster: string) { - const kubeContexts = this.parent.getK8().getContextNames(); + const kubeContexts = this.k8.getContextNames(); return flags.context.prompt(task, kubeContexts, cluster); } @@ -233,7 +259,7 @@ export class ClusterCommandTasks { valuesArg += ` --set cloud.minio.enabled=${minioEnabled}`; if (certManagerEnabled && !certManagerCrdsEnabled) { - this.parent.logger.showUser( + this.logger.showUser( chalk.yellowBright('> WARNING:'), chalk.yellow( 'cert-manager CRDs are required for cert-manager, please enable it if you have not installed it independently.', @@ -246,23 +272,20 @@ export class ClusterCommandTasks { /** Show list of installed chart */ private async showInstalledChartList(clusterSetupNamespace: NamespaceName) { - this.parent.logger.showList( - 'Installed Charts', - await this.parent.getChartManager().getInstalledCharts(clusterSetupNamespace), - ); + this.logger.showList('Installed Charts', await this.chartManager.getInstalledCharts(clusterSetupNamespace)); } - selectContext(): SoloListrTask { + public selectContext(): SoloListrTask { return { title: 'Read local configuration settings', task: async (_, task) => { - this.parent.logger.info('Read local configuration settings...'); - const configManager = this.parent.getConfigManager(); + this.logger.info('Read local configuration settings...'); + const configManager = this.configManager; const isQuiet = configManager.getFlag(flags.quiet); const deploymentName: string = configManager.getFlag(flags.namespace); let clusters = splitFlagInput(configManager.getFlag(flags.clusterName)); const contexts = splitFlagInput(configManager.getFlag(flags.context)); - const localConfig = this.parent.getLocalConfig(); + const localConfig = this.localConfig; let selectedContext: string; let selectedCluster: string; @@ -298,8 +321,8 @@ export class ClusterCommandTasks { else { // Add the deployment to the LocalConfig with the currently selected cluster and context in KubeConfig if (isQuiet) { - selectedContext = this.parent.getK8().getCurrentContext(); - selectedCluster = this.parent.getK8().getCurrentClusterName(); + selectedContext = this.k8.getCurrentContext(); + selectedCluster = this.k8.getCurrentClusterName(); localConfig.deployments[deploymentName] = { clusters: [selectedCluster], }; @@ -326,55 +349,56 @@ export class ClusterCommandTasks { } } - const connectionValid = await this.parent.getK8().testContextConnection(selectedContext); + const connectionValid = await this.k8.testContextConnection(selectedContext); if (!connectionValid) { throw new SoloError(ErrorMessages.INVALID_CONTEXT_FOR_CLUSTER(selectedContext, selectedCluster)); } - this.parent.getK8().setCurrentContext(selectedContext); - this.parent.getConfigManager().setFlag(flags.context, selectedContext); + this.k8.setCurrentContext(selectedContext); + this.configManager.setFlag(flags.context, selectedContext); }, }; } - initialize(argv: any, configInit: ConfigBuilder) { + public initialize(argv: any, configInit: ConfigBuilder) { const {requiredFlags, optionalFlags} = argv; argv.flags = [...requiredFlags, ...optionalFlags]; return new Task('Initialize', async (ctx: any, task: ListrTaskWrapper) => { if (argv[flags.devMode.name]) { - this.parent.logger.setDevMode(true); + this.logger.setDevMode(true); } ctx.config = await configInit(argv, ctx, task); }); } - showClusterList() { + public showClusterList() { return new Task('List all available clusters', async (ctx: any, task: ListrTaskWrapper) => { - this.parent.logger.showList('Clusters', this.parent.getK8().getClusters()); + this.logger.showList('Clusters', this.k8.getClusters()); }); } - getClusterInfo() { + public getClusterInfo() { return new Task('Get cluster info', async (ctx: any, task: ListrTaskWrapper) => { try { - const clusterName = this.parent.getK8().getCurrentClusterName(); - this.parent.logger.showUser(`Cluster Name (${clusterName})`); - this.parent.logger.showUser('\n'); + const clusterName = this.k8.getCurrentClusterName(); + this.logger.showUser(`Cluster Name (${clusterName})`); + this.logger.showUser('\n'); } catch (e: Error | unknown) { - this.parent.logger.showUserError(e); + this.logger.showUserError(e); } }); } - prepareChartValues(argv) { + public prepareChartValues(argv) { const self = this; return new Task( 'Prepare chart values', async (ctx: any, task: ListrTaskWrapper) => { - ctx.chartPath = await this.parent.prepareChartPath( + ctx.chartPath = await prepareChartPath( + this.helm, ctx.config.chartDir, constants.SOLO_TESTING_CHART_URL, constants.SOLO_CLUSTER_SETUP_CHART, @@ -425,8 +449,8 @@ export class ClusterCommandTasks { ); } - installClusterChart(argv) { - const parent = this.parent; + public installClusterChart(argv) { + const self = this; return new Task( `Install '${constants.SOLO_CLUSTER_SETUP_CHART}' chart`, async (ctx: any, task: ListrTaskWrapper) => { @@ -435,18 +459,22 @@ export class ClusterCommandTasks { const valuesArg = ctx.valuesArg; try { - parent.logger.debug(`Installing chart chartPath = ${ctx.chartPath}, version = ${version}`); - await parent - .getChartManager() - .install(clusterSetupNamespace, constants.SOLO_CLUSTER_SETUP_CHART, ctx.chartPath, version, valuesArg); + self.logger.debug(`Installing chart chartPath = ${ctx.chartPath}, version = ${version}`); + await self.chartManager.install( + clusterSetupNamespace, + constants.SOLO_CLUSTER_SETUP_CHART, + ctx.chartPath, + version, + valuesArg, + ); } catch (e: Error | unknown) { // if error, uninstall the chart and rethrow the error - parent.logger.debug( + self.logger.debug( `Error on installing ${constants.SOLO_CLUSTER_SETUP_CHART}. attempting to rollback by uninstalling the chart`, e, ); try { - await parent.getChartManager().uninstall(clusterSetupNamespace, constants.SOLO_CLUSTER_SETUP_CHART); + await self.chartManager.uninstall(clusterSetupNamespace, constants.SOLO_CLUSTER_SETUP_CHART); } catch { // ignore error during uninstall since we are doing the best-effort uninstall here } @@ -462,15 +490,14 @@ export class ClusterCommandTasks { ); } - acquireNewLease(argv) { + public acquireNewLease(argv) { return new Task('Acquire new lease', async (ctx: any, task: ListrTaskWrapper) => { - const lease = await this.parent.getLeaseManager().create(); + const lease = await this.leaseManager.create(); return ListrLease.newAcquireLeaseTask(lease, task); }); } - uninstallClusterChart(argv) { - const parent = this.parent; + public uninstallClusterChart(argv) { const self = this; return new Task( @@ -493,7 +520,7 @@ export class ClusterCommandTasks { } } - await parent.getChartManager().uninstall(clusterSetupNamespace, constants.SOLO_CLUSTER_SETUP_CHART); + await self.chartManager.uninstall(clusterSetupNamespace, constants.SOLO_CLUSTER_SETUP_CHART); if (argv.dev) { await this.showInstalledChartList(clusterSetupNamespace); } diff --git a/src/commands/deployment.ts b/src/commands/deployment.ts index 2d5e88ef9..2f41f8d79 100644 --- a/src/commands/deployment.ts +++ b/src/commands/deployment.ts @@ -24,7 +24,7 @@ export class DeploymentCommand extends BaseCommand { constructor(opts: Opts) { super(opts); - this.tasks = new ClusterCommandTasks(this, this.k8); + this.tasks = container.resolve(ClusterCommandTasks); } private static get DEPLOY_FLAGS_LIST(): CommandFlag[] { diff --git a/src/commands/explorer.ts b/src/commands/explorer.ts index 866f452c6..fb869fd4a 100644 --- a/src/commands/explorer.ts +++ b/src/commands/explorer.ts @@ -14,6 +14,7 @@ import {type Opts} from '../types/command_types.js'; import {ListrLease} from '../core/lease/listr_lease.js'; import {ComponentType} from '../core/config/remote/enumerations.js'; import {MirrorNodeExplorerComponent} from '../core/config/remote/components/mirror_node_explorer_component.js'; +import {prepareChartPath, prepareValuesFiles} from '../core/helpers.js'; import {type SoloListrTask} from '../types/index.js'; import {type NamespaceName} from '../core/kube/namespace_name.js'; import {ClusterChecks} from '../core/cluster_checks.js'; @@ -80,11 +81,11 @@ export class ExplorerCommand extends BaseCommand { const profileName = this.configManager.getFlag(flags.profileName) as string; const profileValuesFile = await this.profileManager.prepareValuesHederaExplorerChart(profileName); if (profileValuesFile) { - valuesArg += this.prepareValuesFiles(profileValuesFile); + valuesArg += prepareValuesFiles(profileValuesFile); } if (config.valuesFile) { - valuesArg += this.prepareValuesFiles(config.valuesFile); + valuesArg += prepareValuesFiles(config.valuesFile); } valuesArg += ` --set proxyPass./api="http://${constants.MIRROR_NODE_RELEASE_NAME}-rest" `; @@ -141,7 +142,7 @@ export class ExplorerCommand extends BaseCommand { async prepareValuesArg(config: ExplorerDeployConfigClass) { let valuesArg = ''; if (config.valuesFile) { - valuesArg += this.prepareValuesFiles(config.valuesFile); + valuesArg += prepareValuesFiles(config.valuesFile); } return valuesArg; } @@ -182,14 +183,15 @@ export class ExplorerCommand extends BaseCommand { return ListrLease.newAcquireLeaseTask(lease, task); }, }, - ListrRemoteConfig.loadRemoteConfig(this, argv), + ListrRemoteConfig.loadRemoteConfig(this.remoteConfigManager, argv), { title: 'Upgrade solo-setup chart', task: async ctx => { const config = ctx.config; const {chartDirectory, clusterSetupNamespace, soloChartVersion} = config; - const chartPath = await this.prepareChartPath( + const chartPath = await prepareChartPath( + self.helm, chartDirectory, constants.SOLO_TESTING_CHART_URL, constants.SOLO_CLUSTER_SETUP_CHART, @@ -238,7 +240,7 @@ export class ExplorerCommand extends BaseCommand { task: async ctx => { const config = ctx.config; - let exploreValuesArg = self.prepareValuesFiles(constants.EXPLORER_VALUES_FILE); + let exploreValuesArg = prepareValuesFiles(constants.EXPLORER_VALUES_FILE); exploreValuesArg += await self.prepareHederaExplorerValuesArg(config); await self.chartManager.install( @@ -346,7 +348,7 @@ export class ExplorerCommand extends BaseCommand { return ListrLease.newAcquireLeaseTask(lease, task); }, }, - ListrRemoteConfig.loadRemoteConfig(this, argv), + ListrRemoteConfig.loadRemoteConfig(this.remoteConfigManager, argv), { title: 'Destroy explorer', task: async ctx => { diff --git a/src/commands/mirror_node.ts b/src/commands/mirror_node.ts index d0e14e64a..fa61740f0 100644 --- a/src/commands/mirror_node.ts +++ b/src/commands/mirror_node.ts @@ -9,7 +9,7 @@ import {type AccountManager} from '../core/account_manager.js'; import {type ProfileManager} from '../core/profile_manager.js'; import {BaseCommand} from './base.js'; import {Flags as flags} from './flags.js'; -import {getEnvValue} from '../core/helpers.js'; +import {getEnvValue, prepareChartPath, prepareValuesFiles} from '../core/helpers.js'; import {type CommandBuilder} from '../types/aliases.js'; import {PodName} from '../core/kube/pod_name.js'; import {type Opts} from '../types/command_types.js'; @@ -99,11 +99,11 @@ export class MirrorNodeCommand extends BaseCommand { const profileName = this.configManager.getFlag(flags.profileName) as string; const profileValuesFile = await this.profileManager.prepareValuesForMirrorNodeChart(profileName); if (profileValuesFile) { - valuesArg += this.prepareValuesFiles(profileValuesFile); + valuesArg += prepareValuesFiles(profileValuesFile); } if (config.valuesFile) { - valuesArg += this.prepareValuesFiles(config.valuesFile); + valuesArg += prepareValuesFiles(config.valuesFile); } if (config.storageBucket) { @@ -162,14 +162,15 @@ export class MirrorNodeCommand extends BaseCommand { 'valuesArg', ]) as MirrorNodeDeployConfigClass; - ctx.config.chartPath = await self.prepareChartPath( + ctx.config.chartPath = await prepareChartPath( + self.helm, '', // don't use chartPath which is for local solo-charts only constants.MIRROR_NODE_RELEASE_NAME, constants.MIRROR_NODE_CHART, ); // predefined values first - ctx.config.valuesArg += this.prepareValuesFiles(constants.MIRROR_NODE_VALUES_FILE); + ctx.config.valuesArg += prepareValuesFiles(constants.MIRROR_NODE_VALUES_FILE); // user defined values later to override predefined values ctx.config.valuesArg += await self.prepareValuesArg(ctx.config); diff --git a/src/commands/network.ts b/src/commands/network.ts index 3e130e7dd..4c0dce749 100644 --- a/src/commands/network.ts +++ b/src/commands/network.ts @@ -9,8 +9,14 @@ import {BaseCommand} from './base.js'; import {Flags as flags} from './flags.js'; import * as constants from '../core/constants.js'; import {Templates} from '../core/templates.js'; -import * as helpers from '../core/helpers.js'; -import {addDebugOptions, resolveValidJsonFilePath, validatePath} from '../core/helpers.js'; +import { + addDebugOptions, + prepareValuesFiles, + resolveValidJsonFilePath, + validatePath, + parseNodeAliases, + prepareChartPath, +} from '../core/helpers.js'; import path from 'path'; import fs from 'fs'; import {type KeyManager} from '../core/key_manager.js'; @@ -306,7 +312,7 @@ export class NetworkCommand extends BaseCommand { const profileName = this.configManager.getFlag(flags.profileName) as string; this.profileValuesFile = await this.profileManager.prepareValuesForSoloChart(profileName); if (this.profileValuesFile) { - valuesArg += this.prepareValuesFiles(this.profileValuesFile); + valuesArg += prepareValuesFiles(this.profileValuesFile); } valuesArg += ` --set "telemetry.prometheus.svcMonitor.enabled=${config.enablePrometheusSvcMonitor}"`; @@ -341,7 +347,7 @@ export class NetworkCommand extends BaseCommand { } if (config.valuesFile) { - valuesArg += this.prepareValuesFiles(config.valuesFile); + valuesArg += prepareValuesFiles(config.valuesFile); } this.logger.debug('Prepared helm chart values', {valuesArg}); @@ -397,7 +403,7 @@ export class NetworkCommand extends BaseCommand { 'resolvedThrottlesFile', ]) as NetworkDeployConfigClass; - config.nodeAliases = helpers.parseNodeAliases(config.nodeAliasesUnparsed); + config.nodeAliases = parseNodeAliases(config.nodeAliasesUnparsed); if (config.haproxyIps) { config.haproxyIpsParsed = Templates.parseNodeAliasToIpMapping(config.haproxyIps); @@ -408,7 +414,8 @@ export class NetworkCommand extends BaseCommand { } // compute values - config.chartPath = await this.prepareChartPath( + config.chartPath = await prepareChartPath( + this.helm, config.chartDirectory, constants.SOLO_TESTING_CHART_URL, constants.SOLO_DEPLOYMENT_CHART, diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index 3b6c15271..df93d39a5 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -98,7 +98,8 @@ export const upgradeConfigBuilder = async function (argv, ctx, task, shouldLoadN // set config in the context for later tasks to use ctx.config = config; - ctx.config.chartPath = await this.prepareChartPath( + ctx.config.chartPath = await helpers.prepareChartPath( + this.helm, ctx.config.chartDirectory, constants.SOLO_TESTING_CHART_URL, constants.SOLO_DEPLOYMENT_CHART, @@ -134,7 +135,8 @@ export const updateConfigBuilder = async function (argv, ctx, task, shouldLoadNo // set config in the context for later tasks to use ctx.config = config; - ctx.config.chartPath = await this.prepareChartPath( + ctx.config.chartPath = await helpers.prepareChartPath( + this.helm, ctx.config.chartDirectory, constants.SOLO_TESTING_CHART_URL, constants.SOLO_DEPLOYMENT_CHART, @@ -177,7 +179,8 @@ export const deleteConfigBuilder = async function (argv, ctx, task, shouldLoadNo // set config in the context for later tasks to use ctx.config = config; - ctx.config.chartPath = await this.prepareChartPath( + ctx.config.chartPath = await helpers.prepareChartPath( + this.helm, ctx.config.chartDirectory, constants.SOLO_TESTING_CHART_URL, constants.SOLO_DEPLOYMENT_CHART, @@ -225,7 +228,8 @@ export const addConfigBuilder = async function (argv, ctx, task, shouldLoadNodeC // set config in the context for later tasks to use ctx.config = config; - ctx.config.chartPath = await this.prepareChartPath( + ctx.config.chartPath = await helpers.prepareChartPath( + this.helm, ctx.config.chartDirectory, constants.SOLO_TESTING_CHART_URL, constants.SOLO_DEPLOYMENT_CHART, diff --git a/src/commands/node/handlers.ts b/src/commands/node/handlers.ts index b7d9b667c..a39d6ddae 100644 --- a/src/commands/node/handlers.ts +++ b/src/commands/node/handlers.ts @@ -20,65 +20,51 @@ import { upgradeConfigBuilder, } from './configs.js'; import * as constants from '../../core/constants.js'; -import {type AccountManager} from '../../core/account_manager.js'; -import {type ConfigManager} from '../../core/config_manager.js'; -import {type PlatformInstaller} from '../../core/platform_installer.js'; +import {AccountManager} from '../../core/account_manager.js'; +import {PlatformInstaller} from '../../core/platform_installer.js'; +import {K8Client} from '../../core/kube/k8_client.js'; import {type K8} from '../../core/kube/k8.js'; -import {type LeaseManager} from '../../core/lease/lease_manager.js'; -import {type RemoteConfigManager} from '../../core/config/remote/remote_config_manager.js'; -import {IllegalArgumentError, SoloError} from '../../core/errors.js'; +import {LeaseManager} from '../../core/lease/lease_manager.js'; +import {RemoteConfigManager} from '../../core/config/remote/remote_config_manager.js'; +import {SoloError} from '../../core/errors.js'; import {ComponentType, ConsensusNodeStates} from '../../core/config/remote/enumerations.js'; -import {type SoloLogger} from '../../core/logging.js'; -import {type NodeCommandTasks} from './tasks.js'; import {type Lease} from '../../core/lease/lease.js'; +import {NodeCommandTasks} from './tasks.js'; import {NodeSubcommandType} from '../../core/enumerations.js'; -import {type BaseCommand, type CommandHandlers} from '../base.js'; import {NodeHelper} from './helper.js'; import {type NodeAlias, type NodeAliases} from '../../types/aliases.js'; import {ConsensusNodeComponent} from '../../core/config/remote/components/consensus_node_component.js'; import {type Listr, type ListrTask} from 'listr2'; import chalk from 'chalk'; -import {type ComponentsDataWrapper} from '../../core/config/remote/components_data_wrapper.js'; -import {type Optional} from '../../types/index.js'; +import type {ComponentsDataWrapper} from '../../core/config/remote/components_data_wrapper.js'; +import type {Optional} from '../../types/index.js'; import {type NamespaceNameAsString} from '../../core/config/remote/types.js'; - -export class NodeCommandHandlers implements CommandHandlers { - private readonly accountManager: AccountManager; - private readonly configManager: ConfigManager; - private readonly platformInstaller: PlatformInstaller; - private readonly logger: SoloLogger; - private readonly k8: K8; - private readonly tasks: NodeCommandTasks; - private readonly leaseManager: LeaseManager; - public readonly remoteConfigManager: RemoteConfigManager; - - private getConfig: any; - private prepareChartPath: any; - - public readonly parent: BaseCommand; - - constructor(opts: any) { - if (!opts || !opts.accountManager) - throw new IllegalArgumentError('An instance of core/AccountManager is required', opts.accountManager); - if (!opts || !opts.configManager) throw new Error('An instance of core/ConfigManager is required'); - if (!opts || !opts.logger) throw new Error('An instance of core/Logger is required'); - if (!opts || !opts.tasks) throw new Error('An instance of NodeCommandTasks is required'); - if (!opts || !opts.k8) throw new Error('An instance of core/K8 is required'); - if (!opts || !opts.platformInstaller) - throw new IllegalArgumentError('An instance of core/PlatformInstaller is required', opts.platformInstaller); - - this.logger = opts.logger; - this.tasks = opts.tasks; - this.accountManager = opts.accountManager; - this.configManager = opts.configManager; - this.k8 = opts.k8; - this.platformInstaller = opts.platformInstaller; - this.leaseManager = opts.leaseManager; - this.remoteConfigManager = opts.remoteConfigManager; - - this.getConfig = opts.parent.getConfig.bind(opts.parent); - this.prepareChartPath = opts.parent.prepareChartPath.bind(opts.parent); - this.parent = opts.parent; +import {inject, injectable} from 'tsyringe-neo'; +import {patchInject} from '../../core/container_helper.js'; +import {CommandHandler} from '../../core/command_handler.js'; + +@injectable() +export class NodeCommandHandlers extends CommandHandler { + private _portForwards: any; + + constructor( + @inject(AccountManager) private readonly accountManager: AccountManager, + @inject(K8Client) private readonly k8: K8, + @inject(PlatformInstaller) private readonly platformInstaller: PlatformInstaller, + @inject(LeaseManager) private readonly leaseManager: LeaseManager, + @inject(RemoteConfigManager) private readonly remoteConfigManager: RemoteConfigManager, + @inject(NodeCommandTasks) private readonly tasks: NodeCommandTasks, + ) { + super(); + + this.accountManager = patchInject(accountManager, AccountManager, this.constructor.name); + this.k8 = patchInject(k8, K8Client, this.constructor.name); + this.platformInstaller = patchInject(platformInstaller, PlatformInstaller, this.constructor.name); + this.leaseManager = patchInject(leaseManager, LeaseManager, this.constructor.name); + this.remoteConfigManager = patchInject(remoteConfigManager, RemoteConfigManager, this.constructor.name); + this.tasks = patchInject(tasks, NodeCommandTasks, this.constructor.name); + + this._portForwards = []; } static readonly ADD_CONTEXT_FILE = 'node-add.json'; @@ -262,7 +248,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, prepareUpgradeConfigBuilder.bind(this), lease), this.tasks.prepareUpgradeZip(), @@ -283,7 +269,7 @@ export class NodeCommandHandlers implements CommandHandlers { async freezeUpgrade(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DEFAULT_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, prepareUpgradeConfigBuilder.bind(this), null), this.tasks.prepareUpgradeZip(), @@ -306,7 +292,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, downloadGeneratedFilesConfigBuilder.bind(this), lease), this.tasks.identifyExistingNodes(), @@ -329,7 +315,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ ...this.updatePrepareTasks(argv, lease), ...this.updateSubmitTransactionsTasks(argv), @@ -351,7 +337,7 @@ export class NodeCommandHandlers implements CommandHandlers { argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_PREPARE_FLAGS); const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ ...this.updatePrepareTasks(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateSaveContextParser), @@ -371,7 +357,7 @@ export class NodeCommandHandlers implements CommandHandlers { async updateSubmitTransactions(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_SUBMIT_TRANSACTIONS_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, updateConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser), @@ -392,7 +378,7 @@ export class NodeCommandHandlers implements CommandHandlers { async updateExecute(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_EXECUTE_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, updateConfigBuilder.bind(this), lease, false), this.tasks.loadContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser), @@ -413,7 +399,7 @@ export class NodeCommandHandlers implements CommandHandlers { async upgradePrepare(argv) { argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_PREPARE_FLAGS); const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ ...this.upgradePrepareTasks(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeSaveContextParser), @@ -432,7 +418,7 @@ export class NodeCommandHandlers implements CommandHandlers { async upgradeSubmitTransactions(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_SUBMIT_TRANSACTIONS_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, upgradeConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser), @@ -453,7 +439,7 @@ export class NodeCommandHandlers implements CommandHandlers { async upgradeExecute(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, upgradeConfigBuilder.bind(this), lease, false), this.tasks.loadContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser), @@ -474,7 +460,7 @@ export class NodeCommandHandlers implements CommandHandlers { async upgrade(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS); const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ ...this.upgradePrepareTasks(argv, lease), ...this.upgradeSubmitTransactionsTasks(argv), @@ -495,7 +481,7 @@ export class NodeCommandHandlers implements CommandHandlers { async delete(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DELETE_FLAGS); const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ ...this.deletePrepareTaskList(argv, lease), ...this.deleteSubmitTransactionsTaskList(argv), @@ -518,7 +504,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ ...this.deletePrepareTaskList(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.DELETE_CONTEXT_FILE, NodeHelper.deleteSaveContextParser), @@ -540,7 +526,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, deleteConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.DELETE_CONTEXT_FILE, NodeHelper.deleteLoadContextParser), @@ -563,7 +549,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, deleteConfigBuilder.bind(this), lease, false), this.tasks.loadContextData(argv, NodeCommandHandlers.DELETE_CONTEXT_FILE, NodeHelper.deleteLoadContextParser), @@ -586,7 +572,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [...this.addPrepareTasks(argv, lease), ...this.addSubmitTransactionsTasks(argv), ...this.addExecuteTasks(argv)], { concurrent: false, @@ -605,7 +591,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ ...this.addPrepareTasks(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addSaveContextParser), @@ -627,7 +613,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, addConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addLoadContextParser), @@ -650,7 +636,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, addConfigBuilder.bind(this), lease, false), this.tasks.identifyExistingNodes(), @@ -671,7 +657,7 @@ export class NodeCommandHandlers implements CommandHandlers { async logs(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.LOGS_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [this.tasks.initialize(argv, logsConfigBuilder.bind(this), null), this.tasks.getNodeLogsAndConfigs()], { concurrent: false, @@ -688,7 +674,7 @@ export class NodeCommandHandlers implements CommandHandlers { async states(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.STATES_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [this.tasks.initialize(argv, statesConfigBuilder.bind(this), null), this.tasks.getNodeStateFiles()], { concurrent: false, @@ -707,7 +693,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, refreshConfigBuilder.bind(this), lease), this.validateAllNodeStates({ @@ -736,7 +722,7 @@ export class NodeCommandHandlers implements CommandHandlers { async keys(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.KEYS_FLAGS); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, keysConfigBuilder.bind(this), null), this.tasks.generateGossipKeys(), @@ -760,7 +746,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, stopConfigBuilder.bind(this), lease), this.validateAllNodeStates({ @@ -787,7 +773,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, startConfigBuilder.bind(this), lease), this.validateAllNodeStates({acceptedStates: [ConsensusNodeStates.SETUP]}), @@ -817,7 +803,7 @@ export class NodeCommandHandlers implements CommandHandlers { const lease = await this.leaseManager.create(); - const action = this.parent.commandActionBuilder( + const action = this.commandActionBuilder( [ this.tasks.initialize(argv, setupConfigBuilder.bind(this), lease), this.validateAllNodeStates({ diff --git a/src/commands/node/index.ts b/src/commands/node/index.ts index ef3f86136..165e46e7c 100644 --- a/src/commands/node/index.ts +++ b/src/commands/node/index.ts @@ -6,10 +6,10 @@ import {IllegalArgumentError} from '../../core/errors.js'; import {type AccountManager} from '../../core/account_manager.js'; import {YargsCommand} from '../../core/yargs_command.js'; import {BaseCommand} from './../base.js'; -import {NodeCommandTasks} from './tasks.js'; import * as NodeFlags from './flags.js'; import {NodeCommandHandlers} from './handlers.js'; import {type Opts} from '../../types/command_types.js'; +import {patchInject} from '../../core/container_helper.js'; /** * Defines the core functionalities of 'node' command @@ -17,9 +17,7 @@ import {type Opts} from '../../types/command_types.js'; export class NodeCommand extends BaseCommand { private readonly accountManager: AccountManager; - public readonly tasks: NodeCommandTasks; public readonly handlers: NodeCommandHandlers; - public _portForwards: any; constructor(opts: Opts) { super(opts); @@ -38,48 +36,17 @@ export class NodeCommand extends BaseCommand { throw new IllegalArgumentError('An instance of CertificateManager is required', opts.certificateManager); this.accountManager = opts.accountManager; - this._portForwards = []; - - this.tasks = new NodeCommandTasks({ - accountManager: opts.accountManager, - configManager: opts.configManager, - logger: opts.logger, - platformInstaller: opts.platformInstaller, - profileManager: opts.profileManager, - k8: opts.k8, - keyManager: opts.keyManager, - chartManager: opts.chartManager, - certificateManager: opts.certificateManager, - parent: this, - }); - - this.handlers = new NodeCommandHandlers({ - accountManager: opts.accountManager, - configManager: opts.configManager, - platformInstaller: opts.platformInstaller, - logger: opts.logger, - k8: opts.k8, - tasks: this.tasks, - parent: this, - leaseManager: opts.leaseManager, - remoteConfigManager: opts.remoteConfigManager, - }); + + this.handlers = patchInject(null, NodeCommandHandlers, this.constructor.name); + } + + close(): Promise { + // no-op + return Promise.resolve(); } - /** - * stops and closes the port forwards - * - calls the accountManager.close() - * - for all portForwards, calls k8.stopPortForward(srv) - */ - async close() { - await this.accountManager.close(); - if (this._portForwards) { - for (const srv of this._portForwards) { - await this.k8.stopPortForward(srv); - } - } - - this._portForwards = []; + getUnusedConfigs(configName: string): string[] { + return this.handlers.getUnusedConfigs(configName); } getCommandDefinition() { diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index d7451ad54..fe364ca8e 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -1,14 +1,15 @@ /** * SPDX-License-Identifier: Apache-2.0 */ -import {type AccountManager} from '../../core/account_manager.js'; -import {type ConfigManager} from '../../core/config_manager.js'; -import {type KeyManager} from '../../core/key_manager.js'; -import {type ProfileManager} from '../../core/profile_manager.js'; -import {type PlatformInstaller} from '../../core/platform_installer.js'; +import {AccountManager} from '../../core/account_manager.js'; +import {ConfigManager} from '../../core/config_manager.js'; +import {KeyManager} from '../../core/key_manager.js'; +import {ProfileManager} from '../../core/profile_manager.js'; +import {PlatformInstaller} from '../../core/platform_installer.js'; +import {K8Client} from '../../core/kube/k8_client.js'; import {type K8} from '../../core/kube/k8.js'; -import {type ChartManager} from '../../core/chart_manager.js'; -import {type CertificateManager} from '../../core/certificate_manager.js'; +import {ChartManager} from '../../core/chart_manager.js'; +import {CertificateManager} from '../../core/certificate_manager.js'; import {Zippy} from '../../core/zippy.js'; import * as constants from '../../core/constants.js'; import { @@ -46,10 +47,11 @@ import { renameAndCopyFile, sleep, splitFlagInput, + prepareValuesFiles, } from '../../core/helpers.js'; import chalk from 'chalk'; import {Flags as flags} from '../flags.js'; -import {type SoloLogger} from '../../core/logging.js'; +import {SoloLogger} from '../../core/logging.js'; import {type Listr, type ListrTaskWrapper} from 'listr2'; import {type ConfigBuilder, type NodeAlias, type NodeAliases, type SkipCheck} from '../../types/aliases.js'; import {PodName} from '../../core/kube/pod_name.js'; @@ -66,58 +68,31 @@ import {PodRef} from '../../core/kube/pod_ref.js'; import {ContainerRef} from '../../core/kube/container_ref.js'; import {NetworkNodes} from '../../core/network_nodes.js'; import {container} from 'tsyringe-neo'; +import {inject, injectable} from 'tsyringe-neo'; +import {patchInject} from '../../core/container_helper.js'; +@injectable() export class NodeCommandTasks { - private readonly accountManager: AccountManager; - private readonly configManager: ConfigManager; - private readonly keyManager: KeyManager; - private readonly profileManager: ProfileManager; - private readonly platformInstaller: PlatformInstaller; - private readonly logger: SoloLogger; - private readonly k8: K8; - private readonly parent: BaseCommand; - private readonly chartManager: ChartManager; - private readonly certificateManager: CertificateManager; - - private readonly prepareValuesFiles: any; - - constructor(opts: { - logger: SoloLogger; - accountManager: AccountManager; - configManager: ConfigManager; - k8: K8; - platformInstaller: PlatformInstaller; - keyManager: KeyManager; - profileManager: ProfileManager; - chartManager: ChartManager; - certificateManager: CertificateManager; - parent: BaseCommand; - }) { - if (!opts || !opts.accountManager) - throw new IllegalArgumentError('An instance of core/AccountManager is required', opts.accountManager as any); - if (!opts || !opts.configManager) throw new Error('An instance of core/ConfigManager is required'); - if (!opts || !opts.logger) throw new Error('An instance of core/Logger is required'); - if (!opts || !opts.k8) throw new Error('An instance of core/K8 is required'); - if (!opts || !opts.platformInstaller) - throw new IllegalArgumentError('An instance of core/PlatformInstaller is required', opts.platformInstaller); - if (!opts || !opts.keyManager) - throw new IllegalArgumentError('An instance of core/KeyManager is required', opts.keyManager); - if (!opts || !opts.profileManager) - throw new IllegalArgumentError('An instance of ProfileManager is required', opts.profileManager); - if (!opts || !opts.certificateManager) - throw new IllegalArgumentError('An instance of CertificateManager is required', opts.certificateManager); - - this.accountManager = opts.accountManager; - this.configManager = opts.configManager; - this.logger = opts.logger; - this.k8 = opts.k8; - - this.platformInstaller = opts.platformInstaller; - this.profileManager = opts.profileManager; - this.keyManager = opts.keyManager; - this.chartManager = opts.chartManager; - this.certificateManager = opts.certificateManager; - this.prepareValuesFiles = opts.parent.prepareValuesFiles.bind(opts.parent); + constructor( + @inject(SoloLogger) private readonly logger: SoloLogger, + @inject(AccountManager) private readonly accountManager: AccountManager, + @inject(ConfigManager) private readonly configManager: ConfigManager, + @inject(K8Client) private readonly k8: K8, + @inject(PlatformInstaller) private readonly platformInstaller: PlatformInstaller, + @inject(KeyManager) private readonly keyManager: KeyManager, + @inject(ProfileManager) private readonly profileManager: ProfileManager, + @inject(ChartManager) private readonly chartManager: ChartManager, + @inject(CertificateManager) private readonly certificateManager: CertificateManager, + ) { + this.logger = patchInject(logger, SoloLogger, this.constructor.name); + this.accountManager = patchInject(accountManager, AccountManager, this.constructor.name); + this.configManager = patchInject(configManager, ConfigManager, this.constructor.name); + this.k8 = patchInject(k8, K8Client, this.constructor.name); + this.platformInstaller = patchInject(platformInstaller, PlatformInstaller, this.constructor.name); + this.keyManager = patchInject(keyManager, KeyManager, this.constructor.name); + this.profileManager = patchInject(profileManager, ProfileManager, this.constructor.name); + this.chartManager = patchInject(chartManager, ChartManager, this.constructor.name); + this.certificateManager = patchInject(certificateManager, CertificateManager, this.constructor.name); } private async _prepareUpgradeZip(stagingDir: string) { @@ -1502,7 +1477,7 @@ export class NodeCommandTasks { path.join(config.stagingDir, 'templates', 'application.properties'), ); if (profileValuesFile) { - valuesArg += self.prepareValuesFiles(profileValuesFile); + valuesArg += prepareValuesFiles(profileValuesFile); } valuesArg = addDebugOptions(valuesArg, config.debugNodeAlias); diff --git a/src/commands/relay.ts b/src/commands/relay.ts index 284d4f47a..7e4be1dd0 100644 --- a/src/commands/relay.ts +++ b/src/commands/relay.ts @@ -9,7 +9,7 @@ import {type ProfileManager} from '../core/profile_manager.js'; import {type AccountManager} from '../core/account_manager.js'; import {BaseCommand} from './base.js'; import {Flags as flags} from './flags.js'; -import {getNodeAccountMap} from '../core/helpers.js'; +import {getNodeAccountMap, prepareChartPath} from '../core/helpers.js'; import {type CommandBuilder, type NodeAliases} from '../types/aliases.js'; import {type Opts} from '../types/command_types.js'; import {ListrLease} from '../core/lease/listr_lease.js'; @@ -72,7 +72,7 @@ export class RelayCommand extends BaseCommand { const profileName = this.configManager.getFlag(flags.profileName) as string; const profileValuesFile = await this.profileManager.prepareValuesForRpcRelayChart(profileName); if (profileValuesFile) { - valuesArg += this.prepareValuesFiles(profileValuesFile); + valuesArg += helpers.prepareValuesFiles(profileValuesFile); } valuesArg += ` --set config.MIRROR_NODE_URL=http://${constants.MIRROR_NODE_RELEASE_NAME}-rest`; @@ -123,7 +123,7 @@ export class RelayCommand extends BaseCommand { valuesArg += ` --set config.HEDERA_NETWORK='${networkJsonString}'`; if (valuesFile) { - valuesArg += this.prepareValuesFiles(valuesFile); + valuesArg += helpers.prepareValuesFiles(valuesFile); } return valuesArg; @@ -229,7 +229,8 @@ export class RelayCommand extends BaseCommand { title: 'Prepare chart values', task: async ctx => { const config = ctx.config; - config.chartPath = await self.prepareChartPath( + config.chartPath = await prepareChartPath( + self.helm, config.chartDirectory, constants.JSON_RPC_RELAY_CHART, constants.JSON_RPC_RELAY_CHART, diff --git a/src/core/command_handler.ts b/src/core/command_handler.ts new file mode 100644 index 000000000..7cd9dd1b8 --- /dev/null +++ b/src/core/command_handler.ts @@ -0,0 +1,96 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + */ +import {inject, injectable} from 'tsyringe-neo'; +import {SoloLogger} from './logging.js'; +import {patchInject} from './container_helper.js'; +import {Listr} from 'listr2'; +import {SoloError} from './errors.js'; +import {type Lease} from './lease/lease.js'; +import * as constants from './constants.js'; +import fs from 'fs'; +import {Task} from './task.js'; +import type {CommandFlag} from '../types/flag_types.js'; +import {ConfigManager} from './config_manager.js'; +import {getConfig} from './config_builder.js'; +import {type BaseCommand} from '../commands/base.js'; + +@injectable() +export class CommandHandler { + protected readonly _configMaps = new Map(); + + constructor( + @inject(SoloLogger) public readonly logger?: SoloLogger, + @inject(ConfigManager) private readonly configManager?: ConfigManager, + ) { + this.logger = patchInject(logger, SoloLogger, this.constructor.name); + this.configManager = patchInject(configManager, ConfigManager, this.constructor.name); + } + + public commandActionBuilder( + actionTasks: any, + options: any, + errorString: string, + lease: Lease | null, + ): (argv: any, handlerObj: CommandHandler) => Promise { + return async function (argv: any, handlerObj: CommandHandler): Promise { + const tasks = new Listr([...actionTasks], options); + + try { + await tasks.run(); + } catch (e: Error | any) { + handlerObj.logger.error(`${errorString}: ${e.message}`, e); + throw new SoloError(`${errorString}: ${e.message}`, e); + } finally { + const promises = []; + + if (lease) promises.push(lease.release()); + await Promise.all(promises); + } + }; + } + + /** + * Setup home directories + * @param dirs a list of directories that need to be created in sequence + */ + public setupHomeDirectory( + dirs: string[] = [ + constants.SOLO_HOME_DIR, + constants.SOLO_LOGS_DIR, + constants.SOLO_CACHE_DIR, + constants.SOLO_VALUES_DIR, + ], + ): string[] { + const self = this; + + try { + dirs.forEach(dirPath => { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, {recursive: true}); + } + self.logger.debug(`OK: setup directory: ${dirPath}`); + }); + } catch (e: Error | any) { + self.logger.error(e); + throw new SoloError(`failed to create directory: ${e.message}`, e); + } + + return dirs; + } + + public setupHomeDirectoryTask(): Task { + return new Task('Setup home directory', async () => { + this.setupHomeDirectory(); + }); + } + + // Config related methods: + public getConfig(configName: string, flags: CommandFlag[], extraProperties: string[] = []): object { + return getConfig(this.configManager, this._configMaps, configName, flags, extraProperties); + } + + public getUnusedConfigs(configName: string): string[] { + return this._configMaps.get(configName).getUnusedConfigs(); + } +} diff --git a/src/core/config/remote/listr_config_tasks.ts b/src/core/config/remote/listr_config_tasks.ts index 094de173d..fa456b1c1 100644 --- a/src/core/config/remote/listr_config_tasks.ts +++ b/src/core/config/remote/listr_config_tasks.ts @@ -7,6 +7,7 @@ import {type Cluster, type Context} from './types.js'; import {type SoloListrTask} from '../../../types/index.js'; import {type AnyObject} from '../../../types/aliases.js'; import {type NamespaceName} from '../../kube/namespace_name.js'; +import {type RemoteConfigManager} from './remote_config_manager.js'; /** * Static class that handles all tasks related to remote config used by other commands. @@ -24,14 +25,17 @@ export class ListrRemoteConfig { /** * Loads the remote config from the config class and performs component validation. * - * @param command - the BaseCommand object on which an action will be performed + * @param remoteConfigManager * @param argv - used to update the last executed command and command history */ - public static loadRemoteConfig(command: BaseCommand, argv: {_: string[]} & AnyObject): SoloListrTask { + public static loadRemoteConfig( + remoteConfigManager: RemoteConfigManager, + argv: {_: string[]} & AnyObject, + ): SoloListrTask { return { title: 'Load remote config', task: async (): Promise => { - await command.getRemoteConfigManager().loadAndValidate(argv); + await remoteConfigManager.loadAndValidate(argv); }, }; } diff --git a/src/core/config_builder.ts b/src/core/config_builder.ts new file mode 100644 index 000000000..bfc517e93 --- /dev/null +++ b/src/core/config_builder.ts @@ -0,0 +1,82 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + */ +import {type ConfigManager} from './config_manager.js'; +import {type CommandFlag} from '../types/flag_types.js'; + +/** + * Dynamically builds a class with properties from the provided list of flags + * and extra properties, will keep track of which properties are used. Call + * getUnusedConfigs() to get an array of unused properties. + */ +export function getConfig( + configManager: ConfigManager, + configMaps: Map, + configName: string, + flags: CommandFlag[], + extraProperties: string[] = [], +): object { + // build the dynamic class that will keep track of which properties are used + const NewConfigClass = class { + private usedConfigs: Map; + constructor() { + // the map to keep track of which properties are used + this.usedConfigs = new Map(); + + // add the flags as properties to this class + flags?.forEach(flag => { + // @ts-ignore + this[`_${flag.constName}`] = configManager.getFlag(flag); + Object.defineProperty(this, flag.constName, { + get() { + this.usedConfigs.set(flag.constName, this.usedConfigs.get(flag.constName) + 1 || 1); + return this[`_${flag.constName}`]; + }, + }); + }); + + // add the extra properties as properties to this class + extraProperties?.forEach(name => { + // @ts-ignore + this[`_${name}`] = ''; + Object.defineProperty(this, name, { + get() { + this.usedConfigs.set(name, this.usedConfigs.get(name) + 1 || 1); + return this[`_${name}`]; + }, + set(value) { + this[`_${name}`] = value; + }, + }); + }); + } + + /** Get the list of unused configurations that were not accessed */ + getUnusedConfigs() { + const unusedConfigs: string[] = []; + + // add the flag constName to the unusedConfigs array if it was not accessed + flags?.forEach(flag => { + if (!this.usedConfigs.has(flag.constName)) { + unusedConfigs.push(flag.constName); + } + }); + + // add the extra properties to the unusedConfigs array if it was not accessed + extraProperties?.forEach(item => { + if (!this.usedConfigs.has(item)) { + unusedConfigs.push(item); + } + }); + return unusedConfigs; + } + }; + + const newConfigInstance = new NewConfigClass(); + + // add the new instance to the configMaps so that it can be used to get the + // unused configurations using the configName from the BaseCommand + configMaps.set(configName, newConfigInstance); + + return newConfigInstance; +} diff --git a/src/core/container_init.ts b/src/core/container_init.ts index a8fc0882b..203d9ddbc 100644 --- a/src/core/container_init.ts +++ b/src/core/container_init.ts @@ -25,6 +25,10 @@ import os from 'os'; import * as version from '../../version.js'; import {NetworkNodes} from './network_nodes.js'; import {ClusterChecks} from './cluster_checks.js'; +import {ClusterCommandHandlers} from '../commands/cluster/handlers.js'; +import {ClusterCommandTasks} from '../commands/cluster/tasks.js'; +import {NodeCommandTasks} from '../commands/node/tasks.js'; +import {NodeCommandHandlers} from '../commands/node/handlers.js'; /** * Container class to manage the dependency injection container @@ -103,6 +107,12 @@ export class Container { container.register(ClusterChecks, {useClass: ClusterChecks}, {lifecycle: Lifecycle.Singleton}); container.register(NetworkNodes, {useClass: NetworkNodes}, {lifecycle: Lifecycle.Singleton}); + // Commands + container.register(ClusterCommandHandlers, {useClass: ClusterCommandHandlers}, {lifecycle: Lifecycle.Singleton}); + container.register(ClusterCommandTasks, {useClass: ClusterCommandTasks}, {lifecycle: Lifecycle.Singleton}); + container.register(NodeCommandHandlers, {useClass: NodeCommandHandlers}, {lifecycle: Lifecycle.Singleton}); + container.register(NodeCommandTasks, {useClass: NodeCommandTasks}, {lifecycle: Lifecycle.Singleton}); + Container.isInitialized = true; } diff --git a/src/core/helpers.ts b/src/core/helpers.ts index b62fe9cd3..02d747f6f 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -5,7 +5,7 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; import util from 'util'; -import {SoloError} from './errors.js'; +import {MissingArgumentError, SoloError} from './errors.js'; import {Templates} from './templates.js'; import {ROOT_DIR} from './constants.js'; import * as constants from './constants.js'; @@ -15,6 +15,8 @@ import {type CommandFlag} from '../types/flag_types.js'; import {type SoloLogger} from './logging.js'; import {type Duration} from './time/duration.js'; import {type NodeAddConfigClass} from '../commands/node/node_add_config.js'; +import {type Helm} from './helm.js'; +import paths from 'path'; export function sleep(duration: Duration) { return new Promise(resolve => { @@ -371,3 +373,29 @@ export function resolveValidJsonFilePath(filePath: string, defaultPath?: string) throw new SoloError(`Invalid JSON data in file: ${filePath}`); } } + +export async function prepareChartPath(helm: Helm, chartDir: string, chartRepo: string, chartReleaseName: string) { + if (!chartRepo) throw new MissingArgumentError('chart repo name is required'); + if (!chartReleaseName) throw new MissingArgumentError('chart release name is required'); + + if (chartDir) { + const chartPath = path.join(chartDir, chartReleaseName); + await helm.dependency('update', chartPath); + return chartPath; + } + + return `${chartRepo}/${chartReleaseName}`; +} + +export function prepareValuesFiles(valuesFile: string) { + let valuesArg = ''; + if (valuesFile) { + const valuesFiles = valuesFile.split(','); + valuesFiles.forEach(vf => { + const vfp = paths.resolve(vf); + valuesArg += ` --values ${vfp}`; + }); + } + + return valuesArg; +} diff --git a/test/e2e/e2e_node_util.ts b/test/e2e/e2e_node_util.ts index 21353ccb8..e70620f14 100644 --- a/test/e2e/e2e_node_util.ts +++ b/test/e2e/e2e_node_util.ts @@ -20,6 +20,7 @@ import {type ListrTaskWrapper} from 'listr2'; import {ConfigManager} from '../../src/core/config_manager.js'; import {type K8} from '../../src/core/kube/k8.js'; import {type NodeCommand} from '../../src/commands/node/index.js'; +import {NodeCommandTasks} from '../../src/commands/node/tasks.js'; import {Duration} from '../../src/core/time/duration.js'; import {container} from 'tsyringe-neo'; import {NamespaceName} from '../../src/core/kube/namespace_name.js'; @@ -125,8 +126,8 @@ export function e2eNodeKeyRefreshTest(testName: string, mode: string, releaseTag function nodePodShouldBeRunning(nodeCmd: NodeCommand, namespace: NamespaceName, nodeAlias: NodeAlias) { it(`${nodeAlias} should be running`, async () => { try { - // @ts-ignore to access tasks which is a private property - expect((await nodeCmd.tasks.checkNetworkNodePod(namespace, nodeAlias)).podName.name).to.equal( + const nodeTasks = container.resolve(NodeCommandTasks); + expect((await nodeTasks.checkNetworkNodePod(namespace, nodeAlias)).podName.name).to.equal( `network-${nodeAlias}-0`, ); } catch (e) { @@ -157,11 +158,12 @@ export function e2eNodeKeyRefreshTest(testName: string, mode: string, releaseTag } function nodeShouldNotBeActive(nodeCmd: NodeCommand, nodeAlias: NodeAlias) { + const nodeTasks = container.resolve(NodeCommandTasks); it(`${nodeAlias} should not be ACTIVE`, async () => { expect(2); try { await expect( - nodeCmd.tasks._checkNetworkNodeActiveness( + nodeTasks._checkNetworkNodeActiveness( namespace, nodeAlias, {title: ''} as ListrTaskWrapper, diff --git a/test/unit/commands/cluster.test.ts b/test/unit/commands/cluster.test.ts index 37a417971..57aa5504b 100644 --- a/test/unit/commands/cluster.test.ts +++ b/test/unit/commands/cluster.test.ts @@ -76,7 +76,7 @@ argv[flags.force.name] = true; argv[flags.clusterSetupNamespace.name] = constants.SOLO_SETUP_NAMESPACE.name; describe('ClusterCommand unit tests', () => { - before(() => { + beforeEach(() => { resetTestContainer(); }); @@ -193,7 +193,23 @@ describe('ClusterCommand unit tests', () => { configManager.getFlag.withArgs(stubbedFlags[i][0]).returns(stubbedFlags[i][1]); } - return { + container.unregister(RemoteConfigManager); + container.registerInstance(RemoteConfigManager, remoteConfigManagerStub); + + container.unregister(K8Client); + container.registerInstance(K8Client, k8Stub); + + const localConfig = new LocalConfig(filePath); + container.unregister(LocalConfig); + container.registerInstance(LocalConfig, localConfig); + + container.unregister(ConfigManager); + container.registerInstance(ConfigManager, configManager); + + container.unregister(SoloLogger); + container.registerInstance(SoloLogger, loggerStub); + + const options = { logger: loggerStub, helm: sandbox.createStubInstance(Helm), k8: k8Stub, @@ -210,16 +226,17 @@ describe('ClusterCommand unit tests', () => { certificateManager: sandbox.createStubInstance(CertificateManager), remoteConfigManager: remoteConfigManagerStub, } as Opts; + + return options; }; describe('updateLocalConfig', () => { async function runUpdateLocalConfigTask(opts) { command = new ClusterCommand(opts); - tasks = new ClusterCommandTasks(command, opts.k8); + tasks = container.resolve(ClusterCommandTasks); - // @ts-expect-error - TS2554: Expected 0 arguments, but got 1. - const taskObj = tasks.updateLocalConfig({}); + const taskObj = tasks.updateLocalConfig(); await taskObj.task({config: {}} as any, sandbox.stub() as unknown as ListrTaskWrapper); return command; @@ -358,7 +375,7 @@ describe('ClusterCommand unit tests', () => { async function runSelectContextTask(opts) { command = new ClusterCommand(opts); - tasks = new ClusterCommandTasks(command, opts.k8); + tasks = container.resolve(ClusterCommandTasks); // @ts-expect-error - TS2554: Expected 0 arguments, but got 1 const taskObj = tasks.selectContext({}); @@ -485,7 +502,7 @@ describe('ClusterCommand unit tests', () => { async function runReadClustersFromRemoteConfigTask(opts) { command = new ClusterCommand(opts); - tasks = new ClusterCommandTasks(command, k8Stub); + tasks = container.resolve(ClusterCommandTasks); const taskObj = tasks.readClustersFromRemoteConfig({}); taskStub = sandbox.stub() as unknown as ListrTaskWrapper; taskStub.newListr = sandbox.stub();