From a31498e266e421aa579bccf81522b7df36855bff Mon Sep 17 00:00:00 2001 From: Jan Milenkov Date: Mon, 27 Jan 2025 20:06:08 +0200 Subject: [PATCH] feat(remote-config): instead of keeping just the command and the subcommand inside the remote config keep the flags as passed (#1208) Signed-off-by: instamenta --- Taskfile.helper.yml | 16 ++--- docs/content/User/SoloCLI.md | 8 +-- src/commands/flags.ts | 61 ++++++++++++++++++- .../config/remote/remote_config_manager.ts | 10 +-- .../config/remote/remote_config_validator.ts | 2 +- src/core/constants.ts | 2 + src/core/helpers.ts | 10 +-- src/index.ts | 5 ++ src/types/flag_types.ts | 1 + test/unit/core/helpers.test.ts | 20 +++++- 10 files changed, 110 insertions(+), 25 deletions(-) diff --git a/Taskfile.helper.yml b/Taskfile.helper.yml index c4289a7b7..c78a91e53 100644 --- a/Taskfile.helper.yml +++ b/Taskfile.helper.yml @@ -167,7 +167,7 @@ tasks: deps: - task: "init" cmds: - - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node keys --gossip-keys --tls-keys --node-aliases-unparsed {{.node_identifiers}} -q --dev + - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node keys --gossip-keys --tls-keys --node-aliases {{.node_identifiers}} -q --dev solo:network:deploy: silent: true @@ -185,7 +185,7 @@ tasks: if [[ "${SOLO_CHART_VERSION}" != "" ]]; then export SOLO_CHART_FLAG='--solo-chart-version ${SOLO_CHART_VERSION}' fi - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- network deploy --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} ${CONSENSUS_NODE_FLAG} ${SOLO_CHART_FLAG} ${VALUES_FLAG} ${SETTINGS_FLAG} ${LOG4J2_FLAG} ${APPLICATION_PROPERTIES_FLAG} ${GENESIS_THROTTLES_FLAG} ${DEBUG_NODE_FLAG} ${SOLO_CHARTS_DIR_FLAG} ${LOAD_BALANCER_FLAG} ${NETWORK_DEPLOY_EXTRA_FLAGS} -q --dev + SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- network deploy --namespace "${SOLO_NAMESPACE}" --node-aliases {{.node_identifiers}} ${CONSENSUS_NODE_FLAG} ${SOLO_CHART_FLAG} ${VALUES_FLAG} ${SETTINGS_FLAG} ${LOG4J2_FLAG} ${APPLICATION_PROPERTIES_FLAG} ${GENESIS_THROTTLES_FLAG} ${DEBUG_NODE_FLAG} ${SOLO_CHARTS_DIR_FLAG} ${LOAD_BALANCER_FLAG} ${NETWORK_DEPLOY_EXTRA_FLAGS} -q --dev - task: "solo:node:setup" solo:node:setup: @@ -198,7 +198,7 @@ tasks: if [[ "${CONSENSUS_NODE_VERSION}" != "" ]]; then export CONSENSUS_NODE_FLAG='--release-tag {{.CONSENSUS_NODE_VERSION}}' fi - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node setup --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} ${CONSENSUS_NODE_FLAG} ${LOCAL_BUILD_FLAG} -q --dev + SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node setup --namespace "${SOLO_NAMESPACE}" --node-aliases {{.node_identifiers}} ${CONSENSUS_NODE_FLAG} ${LOCAL_BUILD_FLAG} -q --dev solo:network:destroy: silent: true @@ -218,7 +218,7 @@ tasks: if [[ "${DEBUG_NODE_ALIAS}" != "" ]]; then export DEBUG_NODE_FLAG="--debug-node-alias {{ .DEBUG_NODE_ALIAS }}" fi - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node start --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} ${DEBUG_NODE_FLAG} -q {{ .CLI_ARGS }} --dev + SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node start --namespace "${SOLO_NAMESPACE}" --node-aliases {{.node_identifiers}} ${DEBUG_NODE_FLAG} -q {{ .CLI_ARGS }} --dev - | if [[ "{{ .use_port_forwards }}" == "true" ]];then echo "Port forwarding for Hedera Network Node: grpc:50211" @@ -233,7 +233,7 @@ tasks: deps: - task: "init" cmds: - - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node stop --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} -q {{ .CLI_ARGS }} --dev + - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node stop --namespace "${SOLO_NAMESPACE}" --node-aliases {{.node_identifiers}} -q {{ .CLI_ARGS }} --dev solo:relay: silent: true @@ -289,8 +289,8 @@ tasks: cmds: - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node prepare-upgrade --namespace "${SOLO_NAMESPACE}" -q --dev - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node freeze-upgrade --namespace "${SOLO_NAMESPACE}" -q --dev - - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node stop --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} -q --dev - - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node start --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} -q --dev + - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node stop --namespace "${SOLO_NAMESPACE}" --node-aliases {{.node_identifiers}} -q --dev + - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node start --namespace "${SOLO_NAMESPACE}" --node-aliases {{.node_identifiers}} -q --dev cluster:create: silent: true @@ -360,7 +360,7 @@ tasks: solo:node:logs: silent: true cmds: - - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node logs --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} -q --dev + - SOLO_HOME_DIR=${SOLO_HOME_DIR} npm run solo -- node logs --namespace "${SOLO_NAMESPACE}" --node-aliases {{.node_identifiers}} -q --dev start: desc: solo node start diff --git a/docs/content/User/SoloCLI.md b/docs/content/User/SoloCLI.md index 08cc3a7b6..ed14f2331 100644 --- a/docs/content/User/SoloCLI.md +++ b/docs/content/User/SoloCLI.md @@ -248,7 +248,7 @@ Kubernetes Cluster : kind-solo-e2e --debug-node-alias Enable default jvm debug port (5005) for the given node id [string] --log4j2-xml log4j2.xml file for node [string] -n, --namespace Namespace [string] - -i, --node-aliases-unparsed Comma separated node aliases (empty means all nodes) [string] + -i, --node-aliases Comma separated node aliases (empty means all nodes) [string] --pvcs Enable persistent volume claims to store data outside the pod, required for node add [boolean] --profile-file Resource profile definition (e.g. custom-spec.yaml) [string] @@ -297,7 +297,7 @@ Kubernetes Cluster : kind-solo-e2e --debug-node-alias Enable default jvm debug port (5005) for the given node id [string] --log4j2-xml log4j2.xml file for node [string] -n, --namespace Namespace [string] - -i, --node-aliases-unparsed Comma separated node aliases (empty means all nodes) [string] + -i, --node-aliases Comma separated node aliases (empty means all nodes) [string] --pvcs Enable persistent volume claims to store data outside the pod, required for node add [boolean] --profile-file Resource profile definition (e.g. custom-spec.yaml) [string] @@ -399,7 +399,7 @@ solo node command is used to manage hedera network nodes, it has the following s -l, --ledger-id Ledger ID (a.k.a. Chain ID) [string] -d, --chart-dir Local chart directory path (e.g. ~/solo-charts/charts [string] -n, --namespace Namespace [string] - -i, --node-aliases-unparsed Comma separated node aliases (empty means all nodes) [string] + -i, --node-aliases Comma separated node aliases (empty means all nodes) [string] --operator-id Operator ID [string] --operator-key Operator Key [string] --profile-file Resource profile definition (e.g. custom-spec.yaml) [string] @@ -417,5 +417,5 @@ solo node command is used to manage hedera network nodes, it has the following s ```text -d, --chart-dir Local chart directory path (e.g. ~/solo-charts/charts [string] -n, --namespace Namespace [string] - -i, --node-aliases-unparsed Comma separated node aliases (empty means all nodes) [string] [string] + -i, --node-aliases Comma separated node aliases (empty means all nodes) [string] [string] ``` diff --git a/src/commands/flags.ts b/src/commands/flags.ts index c3bc3febc..79d945108 100644 --- a/src/commands/flags.ts +++ b/src/commands/flags.ts @@ -24,6 +24,7 @@ import {IllegalArgumentError, SoloError} from '../core/errors.js'; import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; import * as helpers from '../core/helpers.js'; import validator from 'validator'; +import type {AnyObject} from '../types/aliases.js'; export class Flags { private static async prompt( @@ -475,7 +476,7 @@ export class Flags { static readonly nodeAliasesUnparsed: CommandFlag = { constName: 'nodeAliasesUnparsed', - name: 'node-aliases-unparsed', + name: 'node-aliases', definition: { describe: 'Comma separated node aliases (empty means all nodes)', alias: 'i', @@ -621,6 +622,7 @@ export class Flags { describe: 'Operator Key', defaultValue: undefined, type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptOperatorKey(task: ListrTaskWrapper, input: any) { return await Flags.promptText( @@ -641,6 +643,7 @@ export class Flags { describe: 'Show private key information', defaultValue: false, type: 'boolean', + dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptPrivateKey(task: ListrTaskWrapper, input: any) { return await Flags.promptText( @@ -989,6 +992,7 @@ export class Flags { describe: 'path and file name of the private key for signing gossip in PEM key format to be used', defaultValue: '', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1011,6 +1015,7 @@ export class Flags { describe: 'path and file name of the private TLS key to be used', defaultValue: '', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1054,6 +1059,7 @@ export class Flags { describe: 'ED25519 private key for the Hedera account', defaultValue: '', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptPrivateKey(task: ListrTaskWrapper, input: any) { return await Flags.promptText( @@ -1085,6 +1091,7 @@ export class Flags { describe: 'ECDSA private key for the Hedera account', defaultValue: '', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptPrivateKey(task: ListrTaskWrapper, input: any) { return await Flags.promptText( @@ -1326,6 +1333,7 @@ export class Flags { describe: 'Admin key', defaultValue: constants.GENESIS_KEY, type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1528,6 +1536,7 @@ export class Flags { 'with multiple nodes comma seperated)', defaultValue: '', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptGrpcTlsKeyPath(task: ListrTaskWrapper, input: any) { return await Flags.promptText( @@ -1551,6 +1560,7 @@ export class Flags { 'with multiple nodes comma seperated)', defaultValue: '', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptGrpcWebTlsKeyPath(task: ListrTaskWrapper, input: any) { return await Flags.promptText( @@ -1619,6 +1629,7 @@ export class Flags { defaultValue: '', describe: 'storage access key', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1630,6 +1641,7 @@ export class Flags { defaultValue: '', describe: 'storage secret key', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1641,6 +1653,7 @@ export class Flags { defaultValue: '', describe: 'storage endpoint URL', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1652,6 +1665,7 @@ export class Flags { defaultValue: '', describe: 'name of storage bucket', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1663,6 +1677,7 @@ export class Flags { defaultValue: '', describe: 'name of bucket for backing up state files', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1674,6 +1689,7 @@ export class Flags { defaultValue: '', describe: 'path of google credential file in json format', type: 'string', + dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; @@ -1811,4 +1827,47 @@ export class Flags { requiredFlagsWithDisabledPrompt: [Flags.namespace, Flags.cacheDir, Flags.releaseTag], optionalFlags: [Flags.devMode, Flags.quiet], }; + + /** + * Processes the Argv arguments and returns them as string, all with full flag names. + * - removes flags that match the default value. + * - removes flags with undefined and null values. + * - removes boolean flags that are false. + * - masks all sensitive flags with their dataMask property. + */ + public static stringifyArgv(argv: AnyObject): string { + const processedFlags: string[] = []; + + for (const [name, value] of Object.entries(argv)) { + // Remove non-flag data and boolean presence based flags that are false + if (name === '_' || name === '$0' || value === '' || value === false || value === undefined || value === null) { + continue; + } + + // remove flags that use the default value + const flag = Flags.allFlags.find(flag => flag.name === name); + if (!flag || (flag.definition.defaultValue && flag.definition.defaultValue === value)) { + continue; + } + + const flagName = flag.name; + + // if the flag is boolean based, render it without value + if (value === true) { + processedFlags.push(`--${flagName}`); + } + + // if the flag's data is masked, display it without the value + else if (flag.definition.dataMask) { + processedFlags.push(`--${flagName} ${flag.definition.dataMask}`); + } + + // else display the full flag data + else { + processedFlags.push(`--${flagName} ${value}`); + } + } + + return processedFlags.join(' '); + } } diff --git a/src/core/config/remote/remote_config_manager.ts b/src/core/config/remote/remote_config_manager.ts index d1ecb751d..c6b0368f6 100644 --- a/src/core/config/remote/remote_config_manager.ts +++ b/src/core/config/remote/remote_config_manager.ts @@ -38,10 +38,6 @@ import {ErrorMessages} from '../../error_messages.js'; import {CommonFlagsDataWrapper} from './common_flags_data_wrapper.js'; import {type AnyObject} from '../../../types/aliases.js'; -interface ListrContext { - config: object; -} - /** * Uses Kubernetes ConfigMaps to manage the remote configuration data by creating, loading, modifying, * and saving the configuration data to and from a Kubernetes cluster. @@ -211,8 +207,12 @@ export class RemoteConfigManager { await RemoteConfigValidator.validateComponents(self.remoteConfig.components, self.k8); + const additionalCommandData = `Executed by ${self.localConfig.userEmailAddress}: `; + const currentCommand = argv._.join(' '); - self.remoteConfig!.addCommandToHistory(currentCommand); + const commandArguments = flags.stringifyArgv(argv); + + self.remoteConfig!.addCommandToHistory(additionalCommandData + (currentCommand + ' ' + commandArguments).trim()); await self.remoteConfig.flags.handleFlags(argv); diff --git a/src/core/config/remote/remote_config_validator.ts b/src/core/config/remote/remote_config_validator.ts index 52e6ee261..3868139a2 100644 --- a/src/core/config/remote/remote_config_validator.ts +++ b/src/core/config/remote/remote_config_validator.ts @@ -19,7 +19,7 @@ import {SoloError} from '../../errors.js'; import type {K8} from '../../k8.js'; import type {ComponentsDataWrapper} from './components_data_wrapper.js'; -import {type BaseComponent} from './components/base_component.js'; +import type {BaseComponent} from './components/base_component.js'; /** * Static class is used to validate that components in the remote config diff --git a/src/core/constants.ts b/src/core/constants.ts index 18afccf5f..be3c9d64c 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -173,6 +173,8 @@ export const PROFILE_LOCAL = 'local'; export const ALL_PROFILES = [PROFILE_LOCAL, PROFILE_TINY, PROFILE_SMALL, PROFILE_MEDIUM, PROFILE_LARGE]; export const DEFAULT_PROFILE_FILE = path.join(SOLO_CACHE_DIR, 'profiles', 'custom-spec.yaml'); +export const STANDARD_DATAMASK = '***'; + // ------ Hedera SDK Related ------ export const NODE_CLIENT_MAX_ATTEMPTS = +process.env.NODE_CLIENT_MAX_ATTEMPTS || 600; export const NODE_CLIENT_MIN_BACKOFF = +process.env.NODE_CLIENT_MIN_BACKOFF || 1_000; diff --git a/src/core/helpers.ts b/src/core/helpers.ts index c4effc803..201b5109c 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -23,11 +23,11 @@ import {Templates} from './templates.js'; import {ROOT_DIR} from './constants.js'; import * as constants from './constants.js'; import {PrivateKey, ServiceEndpoint} from '@hashgraph/sdk'; -import {type NodeAlias, type NodeAliases} from '../types/aliases.js'; -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 {NodeAlias, NodeAliases} from '../types/aliases.js'; +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'; export function sleep(duration: Duration) { return new Promise(resolve => { diff --git a/src/index.ts b/src/index.ts index c1bbf515a..2a2d856f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -117,12 +117,17 @@ export function main(argv: any) { // update configManager.update(argv); + const currentCommand = argv._.join(' ') as string; + const commandArguments = flags.stringifyArgv(argv); + const commandData = (currentCommand + ' ' + commandArguments).trim(); + logger.showUser( chalk.cyan('\n******************************* Solo *********************************************'), ); logger.showUser(chalk.cyan('Version\t\t\t:'), chalk.yellow(configManager.getVersion())); logger.showUser(chalk.cyan('Kubernetes Context\t:'), chalk.yellow(context.name)); logger.showUser(chalk.cyan('Kubernetes Cluster\t:'), chalk.yellow(clusterName)); + logger.showUser(chalk.cyan('Current Command\t\t:'), chalk.yellow(commandData)); if (configManager.getFlag(flags.namespace) !== undefined) { logger.showUser(chalk.cyan('Kubernetes Namespace\t:'), chalk.yellow(configManager.getFlag(flags.namespace))); } diff --git a/src/types/flag_types.ts b/src/types/flag_types.ts index 574a33111..2f616d1b9 100644 --- a/src/types/flag_types.ts +++ b/src/types/flag_types.ts @@ -31,4 +31,5 @@ export interface Definition { alias?: string; type?: string; disablePrompt?: boolean; + dataMask?: string; } diff --git a/test/unit/core/helpers.test.ts b/test/unit/core/helpers.test.ts index 9f4dc3904..9984c4104 100644 --- a/test/unit/core/helpers.test.ts +++ b/test/unit/core/helpers.test.ts @@ -17,9 +17,9 @@ import {expect} from 'chai'; import {describe, it} from 'mocha'; import each from 'mocha-each'; +import {Flags as flags} from '../../../src/commands/flags.js'; import * as helpers from '../../../src/core/helpers.js'; -import {HEDERA_PLATFORM_VERSION} from '../../../version.js'; describe('Helpers', () => { each([ @@ -46,4 +46,22 @@ describe('Helpers', () => { expect(p.version).not.to.be.null; expect(p.version).to.deep.equal(helpers.packageVersion()); }); + + it('Should parse argv to args with datamask correctly', () => { + const argv = {[flags.googleCredential.name]: 'VALUE'}; + const result = flags.stringifyArgv(argv); + expect(result).to.equal(`--${flags.googleCredential.name} ${flags.googleCredential.definition.dataMask}`); + }); + + it('Should parse argv to args with boolean flag correctly', () => { + const argv = {[flags.quiet.name]: true}; + const result = flags.stringifyArgv(argv); + expect(result).to.equal(`--${flags.quiet.name}`); + }); + + it('Should parse argv to args with flag correctly', () => { + const argv = {[flags.namespace.name]: 'VALUE'}; + const result = flags.stringifyArgv(argv); + expect(result).to.equal(`--${flags.namespace.name} VALUE`); + }); });