UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

201 lines (180 loc) 6.78 kB
/** * SPDX-License-Identifier: Apache-2.0 */ import {inject, injectable} from 'tsyringe-neo'; import {SoloError, MissingArgumentError} from './errors.js'; import {type SoloLogger} from './logging.js'; import {Flags, Flags as flags} from '../commands/flags.js'; import * as paths from 'path'; import * as helpers from './helpers.js'; import type * as yargs from 'yargs'; import {type CommandFlag} from '../types/flag_types.js'; import {type ListrTaskWrapper} from 'listr2'; import {patchInject} from './dependency_injection/container_helper.js'; import * as constants from '../core/constants.js'; import {NamespaceName} from './kube/resources/namespace/namespace_name.js'; import {InjectTokens} from './dependency_injection/inject_tokens.js'; /** * ConfigManager cache command flag values so that user doesn't need to enter the same values repeatedly. * * For example, 'namespace' is usually remains the same across commands once it is entered, and therefore user * doesn't need to enter it repeatedly. However, user should still be able to specify the flag explicitly for any command. */ @injectable() export class ConfigManager { config!: Record<string, any>; constructor(@inject(InjectTokens.SoloLogger) private readonly logger?: SoloLogger) { this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); this.reset(); } /** Reset config */ reset() { this.config = { flags: {}, version: helpers.getSoloVersion(), updatedAt: new Date().toISOString(), }; } /** * Apply the command flags precedence * * It uses the below precedence for command flag values: * 1. User input of the command flag * 2. Default value of the command flag if the command is not 'init'. */ applyPrecedence(argv: yargs.Argv<any>, aliases: any): yargs.Argv<any> { for (const key of Object.keys(aliases)) { const flag = flags.allFlagsMap.get(key); if (flag) { // @ts-ignore if (argv[key] !== undefined) { // argv takes precedence, nothing to do } else if (this.hasFlag(flag)) { // @ts-ignore argv[key] = this.getFlag(flag); } else { // @ts-ignore argv[key] = flag.definition.defaultValue; } } } return argv; } /** Update the config using the argv */ update(argv: object | any = {}) { if (argv && Object.keys(argv).length > 0) { for (const flag of flags.allFlags) { if (argv[flag.name] !== undefined) { let val = argv[flag.name]; switch (flag.definition.type) { case 'string': if (val && (flag.name === flags.chartDirectory.name || flag.name === flags.cacheDir.name)) { this.logger.debug( `Resolving directory path for '${flag.name}': ${val}, to: ${paths.resolve(val)}, note: ~/ is not supported`, ); val = paths.resolve(val); } // if it is a namespace flag then convert it to NamespaceName else if (val && (flag.name === flags.namespace.name || flag.name === flags.clusterSetupNamespace.name)) { if (val instanceof NamespaceName) { this.config.flags[flag.name] = val; break; } this.config.flags[flag.name] = NamespaceName.of(val); break; } this.config.flags[flag.name] = `${val}`; // force convert to string break; case 'number': try { if (flags.integerFlags.has(flag.name)) { this.config.flags[flag.name] = Number.parseInt(val); } else { this.config.flags[flag.name] = Number.parseFloat(val); } } catch (e: Error | any) { throw new SoloError(`invalid number value '${val}': ${e.message}`, e); } break; case 'boolean': this.config.flags[flag.name] = val === true || val === 'true'; // use comparison to enforce boolean value break; case 'StorageType': // @ts-ignore if (!Object.values(constants.StorageType).includes(`${val}`)) { throw new SoloError(`Invalid storage type value '${val}'`); } else { this.config.flags[flag.name] = val; } break; default: throw new SoloError(`Unsupported field type for flag '${flag.name}': ${flag.definition.type}`); } } } // store last command that was run if (argv._) { this.config.lastCommand = argv._; } this.config.updatedAt = new Date().toISOString(); let flagMessage = ''; for (const key of Object.keys(this.config.flags)) { if (this.config.flags[key]) { flagMessage += `${key}=${this.config.flags[key]}, `; } } if (flagMessage) { this.logger.debug(`Updated config with flags: ${flagMessage}`); } } } /** Check if a flag value is set */ hasFlag(flag: CommandFlag) { return this.config.flags[flag.name] !== undefined; } /** * Return the value of the given flag * @returns value of the flag or undefined if flag value is not available */ getFlag<T>(flag: CommandFlag): undefined | T { if (this.config.flags[flag.name] !== undefined) { return this.config.flags[flag.name]; } return undefined; } /** Set value for the flag */ setFlag<T>(flag: CommandFlag, value: T) { if (!flag || !flag.name) throw new MissingArgumentError('flag must have a name'); // if it is a namespace then convert it to NamespaceName if (flag.name === flags.namespace.name || flag.name === flags.clusterSetupNamespace.name) { if (value instanceof NamespaceName) { this.config.flags[flag.name] = value; return; } this.config.flags[flag.name] = NamespaceName.of(value as string); return; } this.config.flags[flag.name] = value; } /** Get package version */ getVersion(): string { return this.config.version; } /** * Run prompts for the given set of flags * @param task task object from listr2 * @param flagList list of flag objects */ async executePrompt(task: ListrTaskWrapper<any, any, any>, flagList: CommandFlag[] = []) { for (const flag of flagList) { if (flag.definition.disablePrompt || flag.prompt === undefined) { continue; } if (this.getFlag(Flags.quiet)) { return; } const input = await flag.prompt(task, this.getFlag(flag)); this.setFlag(flag, input); } } }