UNPKG

@hashgraph/solo

Version:

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

1,657 lines (1,543 loc) 95.6 kB
// SPDX-License-Identifier: Apache-2.0 import * as constants from '../core/constants.js'; import * as version from '../../version.js'; import {type CommandFlag, type CommandFlags} from '../types/flag-types.js'; import fs from 'node:fs'; import {IllegalArgumentError} from '../core/errors/illegal-argument-error.js'; import {SoloError} from '../core/errors/solo-error.js'; import {ListrInquirerPromptAdapter} from '@listr2/prompt-adapter-inquirer'; import { select as selectPrompt, input as inputPrompt, number as numberPrompt, confirm as confirmPrompt, } from '@inquirer/prompts'; import {type AnyListrContext, type AnyObject, type AnyYargs} from '../types/aliases.js'; import {type ClusterReferenceName} from '../types/index.js'; import {type Optional, type SoloListrTaskWrapper} from '../types/index.js'; import {PathEx} from '../business/utils/path-ex.js'; import validator from 'validator'; export class Flags { public static KEY_COMMON: string = '_COMMON_'; private static async prompt( type: 'toggle' | 'input' | 'number', task: SoloListrTaskWrapper<AnyListrContext>, // eslint-disable-next-line @typescript-eslint/no-explicit-any input: any, // eslint-disable-next-line @typescript-eslint/no-explicit-any defaultValue: Optional<any>, promptMessage: string, emptyCheckMessage: Optional<string>, flagName: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise<any> { try { let needsPrompt: boolean = type === 'toggle' ? input === undefined || typeof input !== 'boolean' : !input; needsPrompt = type === 'number' ? typeof input !== 'number' : needsPrompt; if (needsPrompt) { if (!process.stdout.isTTY || !process.stdin.isTTY) { // this is to help find issues with prompts running in non-interactive mode, user should supply quite mode, // or provide all flags required for command throw new SoloError('Cannot prompt for input in non-interactive mode'); } const promptOptions = {default: defaultValue, message: promptMessage}; switch (type) { case 'input': { input = await task.prompt(ListrInquirerPromptAdapter).run(inputPrompt, promptOptions); break; } case 'toggle': { input = await task.prompt(ListrInquirerPromptAdapter).run(confirmPrompt, promptOptions); break; } case 'number': { input = await task.prompt(ListrInquirerPromptAdapter).run(numberPrompt, promptOptions); break; } } } if (emptyCheckMessage && !input) { throw new SoloError(emptyCheckMessage); } return input; } catch (error) { throw new SoloError(`input failed: ${flagName}: ${error.message}`, error); } } private static async promptText( task: SoloListrTaskWrapper<AnyListrContext>, input: string, defaultValue: Optional<string>, promptMessage: string, emptyCheckMessage: string | null, flagName: string, ): Promise<string> { return await Flags.prompt('input', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName); } private static async promptToggle( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, defaultValue: Optional<boolean>, promptMessage: string, emptyCheckMessage: string | null, flagName: string, ): Promise<boolean> { return await Flags.prompt('toggle', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName); } /** * Disable prompts for the given set of flags * @param flags list of flags to disable prompts for */ public static disablePrompts(flags: CommandFlag[]): void { Flags.resetDisabledPrompts(); for (const flag of flags) { if (flag.definition) { flag.definition.disablePrompt = true; } } } /** * Set flag from the flag option * @param y instance of yargs * @param commandFlags a set of command flags * */ public static setRequiredCommandFlags(y: AnyYargs, ...commandFlags: CommandFlag[]): void { for (const flag of commandFlags) { y.option(flag.name, {...flag.definition, demandOption: true}); } } /** * Set flag from the flag option * @param y instance of yargs * @param commandFlags a set of command flags * */ public static setOptionalCommandFlags(y: AnyYargs, ...commandFlags: CommandFlag[]): void { for (const flag of commandFlags) { const defaultValue: string | number | boolean = flag.definition.defaultValue === '' ? undefined : flag.definition.defaultValue; y.option(flag.name, { ...flag.definition, default: defaultValue, }); } } public static readonly devMode: CommandFlag = { constName: 'devMode', name: 'dev', definition: { describe: 'Enable developer mode', defaultValue: constants.SOLO_DEV_OUTPUT, type: 'boolean', }, prompt: undefined, }; public static readonly predefinedAccounts: CommandFlag = { constName: 'predefinedAccounts', name: 'predefined-accounts', definition: { describe: 'Create predefined accounts on network creation', defaultValue: true, type: 'boolean', }, prompt: undefined, }; public static readonly forcePortForward: CommandFlag = { constName: 'forcePortForward', name: 'force-port-forward', definition: { describe: 'Force port forward to access the network services', defaultValue: true, // always use local port-forwarding by default type: 'boolean', }, prompt: undefined, }; public static readonly externalAddress: CommandFlag = { constName: 'externalAddress', name: 'external-address', definition: { describe: 'Bind address for kubectl port-forward (for example 127.0.0.1 or 0.0.0.0)', type: 'string', }, prompt: undefined, }; // list of common flags across commands. command specific flags are defined in the command's module. public static readonly clusterRef: CommandFlag = { constName: 'clusterRef', name: 'cluster-ref', definition: { describe: 'The cluster reference that will be used for referencing the Kubernetes cluster and stored in the local and ' + 'remote configuration for the deployment. For commands that take multiple clusters they can be separated by commas.', alias: 'c', type: 'string', }, prompt: async function promptClusterReference( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.clusterRef.definition.defaultValue as string, 'Enter cluster reference: ', 'cluster reference cannot be empty', Flags.clusterRef.name, ); }, }; public static readonly clusterSetupNamespace: CommandFlag = { constName: 'clusterSetupNamespace', name: 'cluster-setup-namespace', definition: { describe: 'Cluster Setup Namespace', defaultValue: constants.SOLO_SETUP_NAMESPACE.name, alias: 's', type: 'string', }, prompt: async function promptClusterSetupNamespace( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, 'solo-cluster', 'Enter cluster setup namespace name: ', 'cluster setup namespace cannot be empty', Flags.clusterSetupNamespace.name, ); }, }; public static readonly namespace: CommandFlag = { constName: 'namespace', name: 'namespace', definition: { describe: 'Namespace', alias: 'n', type: 'string', }, prompt: async function promptNamespace( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, 'solo', 'Enter namespace name: ', 'namespace cannot be empty', Flags.namespace.name, ); }, }; public static readonly mirrorNamespace: CommandFlag = { constName: 'mirrorNamespace', name: 'mirror-namespace', definition: { describe: 'Namespace to use for the Mirror Node deployment, a new one will be created if it does not exist', type: 'string', }, prompt: undefined, }; /** * Parse the values files input string that includes the cluster reference and the values file path * <p>It supports input as below: * <p>--values-file aws-cluster=aws/solo-values.yaml,aws-cluster=aws/solo-values2.yaml,gcp-cluster=gcp/solo-values.yaml,gcp-cluster=gcp/solo-values2.yaml * @param input */ public static parseValuesFilesInput(input: string): Record<ClusterReferenceName, Array<string>> { const valuesFiles: Record<ClusterReferenceName, Array<string>> = {}; if (input) { const inputItems: string[] = input.split(','); for (const v of inputItems) { const parts: string[] = v.split('='); let clusterReference: string; let valuesFile: string; if (parts.length === 2) { clusterReference = parts[0]; valuesFile = PathEx.resolve(parts[1]); } else { valuesFile = PathEx.resolve(v); clusterReference = Flags.KEY_COMMON; } if (!valuesFiles[clusterReference]) { valuesFiles[clusterReference] = []; } valuesFiles[clusterReference].push(valuesFile); } } return valuesFiles; } public static readonly valuesFile: CommandFlag = { constName: 'valuesFile', name: 'values-file', definition: { describe: 'Comma separated chart values file', defaultValue: '', alias: 'f', type: 'string', }, prompt: async function promptValuesFile(_: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> { return input; // no prompt is needed for values file }, }; public static readonly networkDeploymentValuesFile: CommandFlag = { constName: 'valuesFile', name: 'values-file', definition: { describe: 'Comma separated chart values file paths for each cluster (e.g. values.yaml,cluster-1=./a/b/values1.yaml,cluster-2=./a/b/values2.yaml)', defaultValue: '', alias: 'f', type: 'string', }, prompt: async function promptValuesFile(_: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> { if (input) { Flags.parseValuesFilesInput(input); // validate input as early as possible by parsing it } return input; // no prompt is needed for values file }, }; public static readonly deployPrometheusStack: CommandFlag = { constName: 'deployPrometheusStack', name: 'prometheus-stack', definition: { describe: 'Deploy prometheus stack', defaultValue: false, type: 'boolean', }, prompt: async function promptDeployPrometheusStack( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.deployPrometheusStack.definition.defaultValue as boolean, 'Would you like to deploy prometheus stack? ', undefined, Flags.deployPrometheusStack.name, ); }, }; public static readonly deployMinio: CommandFlag = { constName: 'deployMinio', name: 'minio', definition: { describe: 'Deploy minio operator', defaultValue: true, type: 'boolean', }, prompt: async function promptDeployMinio( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.deployMinio.definition.defaultValue as boolean, 'Would you like to deploy MinIO? ', undefined, Flags.deployMinio.name, ); }, }; public static readonly deployMetricsServer: CommandFlag = { constName: 'deployMetricsServer', name: 'metrics-server', definition: { describe: 'Deploy metrics server to enable kubectl top for CPU and memory usage monitoring', defaultValue: false, type: 'boolean', }, prompt: undefined, }; public static readonly deployCertManager: CommandFlag = { constName: 'deployCertManager', name: 'cert-manager', definition: { describe: 'Deploy cert manager, also deploys acme-cluster-issuer', defaultValue: false, type: 'boolean', }, prompt: async function promptDeployCertManager( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.deployCertManager.definition.defaultValue as boolean, 'Would you like to deploy Cert Manager? ', undefined, Flags.deployCertManager.name, ); }, }; /* Deploy cert manager CRDs separately from cert manager itself. Cert manager CRDs are required for cert manager to deploy successfully. */ public static readonly deployCertManagerCrds: CommandFlag = { constName: 'deployCertManagerCrds', name: 'cert-manager-crds', definition: { describe: 'Deploy cert manager CRDs', defaultValue: false, type: 'boolean', }, prompt: async function promptDeployCertManagerCrds( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.deployCertManagerCrds.definition.defaultValue as boolean, 'Would you like to deploy Cert Manager CRDs? ', undefined, Flags.deployCertManagerCrds.name, ); }, }; public static readonly deployJsonRpcRelay: CommandFlag = { constName: 'deployJsonRpcRelay', name: 'json-rpc-relay', definition: { describe: 'Deploy JSON RPC Relay', defaultValue: false, alias: 'j', type: 'boolean', }, prompt: undefined, }; public static readonly stateFile: CommandFlag = { constName: 'stateFile', name: 'state-file', definition: { describe: 'A zipped state file to be used for the network', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly upgradeZipFile: CommandFlag = { constName: 'upgradeZipFile', name: 'upgrade-zip-file', definition: { describe: 'A zipped file used for network upgrade', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly releaseTag: CommandFlag = { constName: 'releaseTag', name: 'release-tag', definition: { describe: `Release tag to be used (e.g. ${version.HEDERA_PLATFORM_VERSION})`, alias: 't', defaultValue: version.HEDERA_PLATFORM_VERSION, type: 'string', }, prompt: async function promptReleaseTag( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, version.HEDERA_PLATFORM_VERSION, 'Enter release version: ', undefined, Flags.releaseTag.name, ); }, }; public static readonly upgradeVersion: CommandFlag = { constName: 'upgradeVersion', name: 'upgrade-version', definition: { describe: 'Version to be used for the upgrade', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly imageTag: CommandFlag = { constName: 'imageTag', name: 'image-tag', definition: { describe: 'The Docker image tag to override what is in the Helm Chart', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly componentImage: CommandFlag = { constName: 'componentImage', name: 'component-image', definition: { describe: 'Full Docker image reference override (e.g. ghcr.io/org/image:tag, docker.io/library/redis:7, redis:7)', defaultValue: '', type: 'string', alias: 'relay-image', }, prompt: undefined, }; public static readonly relayReleaseTag: CommandFlag = { constName: 'relayReleaseTag', name: 'relay-release', definition: { describe: 'Relay release tag to be used (e.g. v0.48.0)', defaultValue: version.HEDERA_JSON_RPC_RELAY_VERSION, type: 'string', }, prompt: async function promptRelayReleaseTag( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.relayReleaseTag.definition.defaultValue as string, 'Enter relay release version: ', 'relay-release-tag cannot be empty', Flags.relayReleaseTag.name, ); }, }; public static readonly cacheDir: CommandFlag = { constName: 'cacheDir', name: 'cache-dir', definition: { describe: 'Local cache directory', defaultValue: constants.SOLO_CACHE_DIR, type: 'string', }, prompt: async function promptCacheDirectory( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, constants.SOLO_CACHE_DIR, 'Enter local cache directory path: ', undefined, Flags.cacheDir.name, ); }, }; public static readonly nodeAliasesUnparsed: CommandFlag = { constName: 'nodeAliasesUnparsed', name: 'node-aliases', definition: { describe: 'Comma separated node aliases (empty means all nodes)', alias: 'i', type: 'string', }, prompt: async function promptNodeAliases( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.prompt( 'input', task, input, 'node1,node2,node3', 'Enter list of node IDs (comma separated list): ', undefined, Flags.nodeAliasesUnparsed.name, ); }, }; public static readonly force: CommandFlag = { constName: 'force', name: 'force', definition: { describe: 'Force actions even if those can be skipped', defaultValue: false, type: 'boolean', }, prompt: async function promptForce(task: SoloListrTaskWrapper<AnyListrContext>, input: boolean): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.force.definition.defaultValue as boolean, 'Would you like to force changes? ', undefined, Flags.force.name, ); }, }; public static readonly forceBlockNodeIntegration: CommandFlag = { constName: 'forceBlockNodeIntegration', name: 'force', definition: { describe: 'Force enable block node integration bypassing the version requirements CN >= v0.72.0, BN >= 0.29.0, CN >= 0.150.0', defaultValue: false, type: 'boolean', }, prompt: undefined, }; public static readonly javaFlightRecorderConfiguration: CommandFlag = { constName: 'javaFlightRecorderConfiguration', name: 'jfr-config', definition: { describe: 'Java Flight Recorder configuration file path', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly chartDirectory: CommandFlag = { constName: 'chartDirectory', name: 'chart-dir', definition: { describe: 'Local chart directory path (e.g. ~/solo-charts/charts)', defaultValue: '', type: 'string', }, prompt: async function promptChartDirectory( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { if (input === 'false') { return ''; } try { if (input && !fs.existsSync(input)) { input = await task.prompt(ListrInquirerPromptAdapter).run(inputPrompt, { default: Flags.chartDirectory.definition.defaultValue as string, message: 'Enter local charts directory path: ', }); if (!fs.existsSync(input)) { throw new IllegalArgumentError('Invalid chart directory', input); } } return input; } catch (error) { throw new SoloError(`input failed: ${Flags.chartDirectory.name}`, error); } }, }; public static readonly relayChartDirectory: CommandFlag = { constName: 'relayChartDirectory', name: 'relay-chart-dir', definition: { describe: 'Relay local chart directory path (e.g. ~/hiero-json-rpc-relay/charts)', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly explorerChartDirectory: CommandFlag = { constName: 'explorerChartDirectory', name: 'explorer-chart-dir', definition: { describe: 'Explorer local chart directory path (e.g. ~/hiero-mirror-node-explorer/charts)', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly blockNodeChartDirectory: CommandFlag = { constName: 'blockNodeChartDirectory', name: 'block-node-chart-dir', definition: { describe: 'Block node local chart directory path (e.g. ~/hiero-block-node/charts)', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly blockNodeTssOverlay: CommandFlag = { constName: 'blockNodeTssOverlay', name: 'block-node-tss-overlay', definition: { describe: 'Force-apply block-node TSS values overlay when deploying block nodes before consensus deployment sets tssEnabled in remote config.', defaultValue: false, type: 'boolean', }, prompt: undefined, }; public static readonly blockNodeMapping: CommandFlag = { constName: 'blockNodeIds', name: 'block-node-mapping', definition: { describe: Flags.renderBlockNodeMappingDescription('block-node'), type: 'string', }, prompt: undefined, }; public static readonly externalBlockNodeMapping: CommandFlag = { constName: 'externalBlockNodeIds', name: 'external-block-node-mapping', definition: { describe: Flags.renderBlockNodeMappingDescription('external-block-node'), type: 'string', }, prompt: undefined, }; public static renderBlockNodeMappingDescription(name: 'block-node' | 'external-block-node'): string { return ( `Configure ${name} priority mapping.` + ` Default: all ${name} included, first's priority is 2.` + ` Unlisted ${name} will not routed to the consensus node node.` + ` Example: --${name}-mapping 1=2,2=1` ); } public static readonly mirrorNodeChartDirectory: CommandFlag = { constName: 'mirrorNodeChartDirectory', name: 'mirror-node-chart-dir', definition: { describe: 'Mirror node local chart directory path (e.g. ~/hiero-mirror-node/charts). ' + 'NOTE: This only provides the Helm chart templates — it does NOT make the chart images available to the cluster. ' + 'All container images referenced by the chart must already be pullable (e.g. published to a registry or loaded ' + 'into the cluster with `kind load docker-image`). Using a local branch chart with SNAPSHOT image tags will ' + 'cause pods to fail with ImagePullBackOff unless those images have been built and pushed to a registry or ' + 'loaded into the cluster.', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly replicaCount: CommandFlag = { constName: 'replicaCount', name: 'replica-count', definition: { describe: 'Replica count', defaultValue: 1, alias: '', type: 'number', }, prompt: async function promptReplicaCount( task: SoloListrTaskWrapper<AnyListrContext>, input: number, ): Promise<number> { return await Flags.prompt( 'number', task, input, Flags.replicaCount.definition.defaultValue, 'How many replica do you want? ', undefined, Flags.replicaCount.name, ); }, }; public static readonly id: CommandFlag = { constName: 'id', name: 'id', definition: { describe: 'The numeric identifier for the component', type: 'number', }, prompt: async function (task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<number> { return await Flags.prompt('number', task, input, undefined, 'Enter component id: ', undefined, Flags.id.name); }, }; public static readonly grpcWebEndpoints: CommandFlag = { constName: 'grpcWebEndpoints', name: 'grpc-web-endpoints', definition: { describe: 'Configure gRPC Web endpoints mapping, comma separated' + `\n(Default port: ${constants.GRPC_WEB_PORT ?? 8080})` + '\n(Aliases can be provided explicitly, or inferred by node id order)' + '\n[Format: <alias>=<address>[:<port>][,<alias>=<address>[:<port>]]]' + '\nExamples:' + '\n\tnode1=127.0.0.1:8080,node2=127.0.0.1:8081' + '\n\tnode1=localhost,node2=localhost:8081' + '\n\tlocalhost,127.0.0.2:8081', type: 'string', }, prompt: undefined, }; public static readonly grpcWebEndpoint: CommandFlag = { constName: 'grpcWebEndpoint', name: 'grpc-web-endpoint', definition: { describe: 'Configure gRPC Web endpoint' + `\n(Default port: ${constants.GRPC_WEB_PORT ?? 8080})` + '\n[Format: <address>[:<port>]]', type: 'string', }, prompt: undefined, }; public static readonly mirrorNodeId: CommandFlag = { constName: 'mirrorNodeId', name: 'mirror-node-id', definition: { describe: 'The id of the mirror node which to connect', type: 'number', }, prompt: async function (task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<number> { return await Flags.prompt( 'number', task, input, undefined, 'Enter mirror node id: ', undefined, Flags.mirrorNodeId.name, ); }, }; public static readonly chainId: CommandFlag = { constName: 'chainId', name: 'chain-id', definition: { describe: 'Chain ID', defaultValue: constants.HEDERA_CHAIN_ID, // Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay#configuration alias: 'l', type: 'string', }, prompt: async function promptChainId(task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> { return await Flags.promptText( task, input, Flags.chainId.definition.defaultValue as string, 'Enter chain ID: ', undefined, Flags.chainId.name, ); }, }; // Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/configuration.md public static readonly operatorId: CommandFlag = { constName: 'operatorId', name: 'operator-id', definition: { describe: 'Operator ID', defaultValue: undefined, type: 'string', }, prompt: async function promptOperatorId( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.operatorId.definition.defaultValue as string, 'Enter operator ID: ', undefined, Flags.operatorId.name, ); }, }; // Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/configuration.md public static readonly operatorKey: CommandFlag = { constName: 'operatorKey', name: 'operator-key', definition: { describe: 'Operator Key', defaultValue: undefined, type: 'string', dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptOperatorKey( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.operatorKey.definition.defaultValue as string, 'Enter operator private key: ', undefined, Flags.operatorKey.name, ); }, }; public static readonly privateKey: CommandFlag = { constName: 'privateKey', name: 'private-key', definition: { describe: 'Show private key information', defaultValue: false, type: 'boolean', dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptPrivateKey( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.ed25519PrivateKey.definition.defaultValue as string, 'Enter the private key: ', undefined, Flags.ed25519PrivateKey.name, ); }, }; public static readonly generateGossipKeys: CommandFlag = { constName: 'generateGossipKeys', name: 'gossip-keys', definition: { describe: 'Generate gossip keys for nodes', defaultValue: false, type: 'boolean', }, prompt: async function promptGenerateGossipKeys( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.generateGossipKeys.definition.defaultValue as boolean, `Would you like to generate Gossip keys? ${typeof input} ${input} `, undefined, Flags.generateGossipKeys.name, ); }, }; public static readonly generateTlsKeys: CommandFlag = { constName: 'generateTlsKeys', name: 'tls-keys', definition: { describe: 'Generate gRPC TLS keys for nodes', defaultValue: false, type: 'boolean', }, prompt: async function promptGenerateTLSKeys( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.generateTlsKeys.definition.defaultValue as boolean, 'Would you like to generate TLS keys? ', undefined, Flags.generateTlsKeys.name, ); }, }; public static readonly enableTimeout: CommandFlag = { constName: 'enableTimeout', name: 'enable-timeout', definition: { describe: 'enable time out for running a command', defaultValue: false, type: 'boolean', }, prompt: undefined, }; public static readonly tlsClusterIssuerType: CommandFlag = { constName: 'tlsClusterIssuerType', name: 'tls-cluster-issuer-type', definition: { describe: 'The TLS cluster issuer type to use for hedera explorer, defaults to "self-signed", the available options are: "acme-staging", "acme-prod", or "self-signed"', defaultValue: 'self-signed', type: 'string', }, prompt: async function promptTlsClusterIssuerType( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string | void> { if (input) { return; } try { input = (await task.prompt(ListrInquirerPromptAdapter).run(selectPrompt, { default: Flags.tlsClusterIssuerType.definition.defaultValue as string, message: 'Enter TLS cluster issuer type, available options are: "acme-staging", "acme-prod", or "self-signed":', choices: ['acme-staging', 'acme-prod', 'self-signed'], })) as string; return input; } catch (error) { throw new SoloError(`input failed: ${Flags.tlsClusterIssuerType.name}`, error); } }, }; public static readonly enableExplorerTls: CommandFlag = { constName: 'enableExplorerTls', name: 'enable-explorer-tls', definition: { describe: 'Enable Explorer TLS, defaults to false, requires certManager and certManagerCrds, which can be deployed through solo-cluster-setup chart or standalone', defaultValue: false, type: 'boolean', }, prompt: async function promptEnableExplorerTls( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.enableExplorerTls.definition.defaultValue as boolean, 'Would you like to enable the Explorer TLS? ', undefined, Flags.enableExplorerTls.name, ); }, }; public static readonly ingressControllerValueFile: CommandFlag = { constName: 'ingressControllerValueFile', name: 'ingress-controller-value-file', definition: { describe: 'The value file to use for ingress controller, defaults to ""', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly explorerStaticIp: CommandFlag = { constName: 'explorerStaticIp', name: 'explorer-static-ip', definition: { describe: 'The static IP address to use for the Explorer load balancer, defaults to ""', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly explorerTlsHostName: CommandFlag = { constName: 'explorerTlsHostName', name: 'explorer-tls-host-name', definition: { describe: 'The host name to use for the Explorer TLS, defaults to "explorer.solo.local"', defaultValue: 'explorer.solo.local', type: 'string', }, prompt: async function promptExplorerTlsHostName( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.explorerTlsHostName.definition.defaultValue as string, 'Enter the host name to use for the Explorer TLS: ', undefined, Flags.explorerTlsHostName.name, ); }, }; public static readonly enableMonitoringSupport: CommandFlag = { constName: 'enableMonitoringSupport', name: 'enable-monitoring-support', definition: { describe: 'Enables CRDs for Prometheus and Grafana.', defaultValue: true, type: 'boolean', }, prompt: undefined, }; public static readonly deletePvcs: CommandFlag = { constName: 'deletePvcs', name: 'delete-pvcs', definition: { describe: 'Delete the persistent volume claims. If both --delete-pvcs and --delete-secrets are set to true, the namespace will be deleted.', defaultValue: false, type: 'boolean', }, prompt: async function promptDeletePvcs( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.deletePvcs.definition.defaultValue as boolean, 'Would you like to delete persistent volume claims upon uninstall? ', undefined, Flags.deletePvcs.name, ); }, }; public static readonly deleteSecrets: CommandFlag = { constName: 'deleteSecrets', name: 'delete-secrets', definition: { describe: 'Delete the network secrets. If both --delete-pvcs and --delete-secrets are set to true, the namespace will be deleted.', defaultValue: false, type: 'boolean', }, prompt: async function promptDeleteSecrets( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.deleteSecrets.definition.defaultValue as boolean, 'Would you like to delete secrets upon uninstall? ', undefined, Flags.deleteSecrets.name, ); }, }; public static readonly soloChartVersion: CommandFlag = { constName: 'soloChartVersion', name: 'solo-chart-version', definition: { describe: 'Solo testing chart version', defaultValue: version.SOLO_CHART_VERSION, type: 'string', }, prompt: async function promptSoloChartVersion( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.soloChartVersion.definition.defaultValue as string, 'Enter solo testing chart version: ', undefined, Flags.soloChartVersion.name, ); }, }; public static readonly blockNodeChartVersion: CommandFlag = { constName: 'chartVersion', name: 'chart-version', definition: { describe: 'Block nodes chart version', defaultValue: version.BLOCK_NODE_VERSION, type: 'string', }, prompt: undefined, }; public static readonly priorityMapping: CommandFlag = { constName: 'priorityMapping', name: 'priority-mapping', definition: { describe: 'Configure block node priority mapping.' + ' Unlisted nodes will not be routed to a block node' + ' Default: all consensus nodes included, first node priority is 2.' + ' Example: "priority-mapping node1=2,node2=1"', type: 'string', }, prompt: undefined, }; public static readonly externalBlockNodeAddress: CommandFlag = { constName: 'externalBlockNodeAddress', name: 'address', definition: { describe: 'Provide external block node address (IP or domain), with optional port' + ` (Default port: ${constants.BLOCK_NODE_PORT})` + ' Examples: "--address localhost:8080", "--address 192.0.0.1"', type: 'string', }, prompt: undefined, }; public static readonly wrapsEnabled: CommandFlag = { constName: 'wrapsEnabled', name: 'wraps', definition: { describe: 'Enable recursive WRAPs aggregation for hinTS/TSS (CN >= v0.72).', type: 'boolean', defaultValue: false, }, prompt: undefined, }; public static readonly wrapsKeyPath: CommandFlag = { constName: 'wrapsKeyPath', name: 'wraps-key-path', definition: { describe: 'Path to a local directory containing pre-existing WRAPs proving key files (.bin)', type: 'string', }, prompt: undefined, }; public static readonly tssEnabled: CommandFlag = { constName: 'tssEnabled', name: 'tss', definition: { describe: 'Enable hinTS/TSS (CN >= v0.72).', type: 'boolean', defaultValue: true, }, prompt: undefined, }; public static readonly applicationProperties: CommandFlag = { constName: 'applicationProperties', name: 'application-properties', definition: { describe: 'application.properties file for node', defaultValue: PathEx.join('templates', constants.APPLICATION_PROPERTIES), type: 'string', }, prompt: undefined, }; public static readonly applicationEnv: CommandFlag = { constName: 'applicationEnv', name: 'application-env', definition: { describe: 'the application.env file for the node provides environment variables to the solo-container' + ' to be used when the hedera platform is started', defaultValue: PathEx.join('templates', 'application.env'), type: 'string', }, prompt: undefined, }; public static readonly apiPermissionProperties: CommandFlag = { constName: 'apiPermissionProperties', name: 'api-permission-properties', definition: { describe: 'api-permission.properties file for node', defaultValue: PathEx.join('templates', 'api-permission.properties'), type: 'string', }, prompt: undefined, }; public static readonly bootstrapProperties: CommandFlag = { constName: 'bootstrapProperties', name: 'bootstrap-properties', definition: { describe: 'bootstrap.properties file for node', defaultValue: PathEx.join('templates', 'bootstrap.properties'), type: 'string', }, prompt: undefined, }; public static readonly genesisThrottlesFile: CommandFlag = { constName: 'genesisThrottlesFile', name: 'genesis-throttles-file', definition: { describe: 'throttles.json file used during network genesis', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly settingTxt: CommandFlag = { constName: 'settingTxt', name: 'settings-txt', definition: { describe: 'settings.txt file for node', defaultValue: PathEx.join('templates', 'settings.txt'), type: 'string', }, prompt: undefined, }; public static readonly app: CommandFlag = { constName: 'app', name: 'app', definition: { describe: 'Testing app name', defaultValue: constants.HEDERA_APP_NAME, type: 'string', }, prompt: undefined, }; public static readonly appConfig: CommandFlag = { constName: 'appConfig', name: 'app-config', definition: { describe: 'json config file of testing app', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly localBuildPath: CommandFlag = { constName: 'localBuildPath', name: 'local-build-path', definition: { describe: 'path of hedera local repo', defaultValue: constants.getEnvironmentVariable('SOLO_LOCAL_BUILD_PATH') || '', type: 'string', }, prompt: undefined, }; public static readonly newAccountNumber: CommandFlag = { constName: 'newAccountNumber', name: 'new-account-number', definition: { describe: 'new account number for node update transaction', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly newAdminKey: CommandFlag = { constName: 'newAdminKey', name: 'new-admin-key', definition: { describe: 'new admin key for the Hedera account', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly gossipPublicKey: CommandFlag = { constName: 'gossipPublicKey', name: 'gossip-public-key', definition: { describe: 'path and file name of the public key for signing gossip in PEM key format to be used', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly gossipPrivateKey: CommandFlag = { constName: 'gossipPrivateKey', name: 'gossip-private-key', definition: { 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, }; public static readonly tlsPublicKey: CommandFlag = { constName: 'tlsPublicKey', name: 'tls-public-key', definition: { describe: 'path and file name of the public TLS key to be used', defaultValue: '', type: 'string', }, prompt: undefined, }; public static readonly tlsPrivateKey: CommandFlag = { constName: 'tlsPrivateKey', name: 'tls-private-key', definition: { describe: 'path and file name of the private TLS key to be used', defaultValue: '', type: 'string', dataMask: constants.STANDARD_DATAMASK, }, prompt: undefined, }; public static readonly log4j2Xml: CommandFlag = { constName: 'log4j2Xml', name: 'log4j2-xml', definition: { describe: 'log4j2.xml file for node', defaultValue: PathEx.join('templates', 'log4j2.xml'), type: 'string', }, prompt: undefined, }; public static readonly updateAccountKeys: CommandFlag = { constName: 'updateAccountKeys', name: 'update-account-keys', definition: { describe: 'Updates the special account keys to new keys and stores their keys in a corresponding Kubernetes secret', defaultValue: true, type: 'boolean', }, prompt: async function promptUpdateAccountKeys( task: SoloListrTaskWrapper<AnyListrContext>, input: boolean, ): Promise<boolean> { return await Flags.promptToggle( task, input, Flags.updateAccountKeys.definition.defaultValue as boolean, 'Would you like to updates the special account keys to new keys and stores their keys in a corresponding Kubernetes secret? ', undefined, Flags.updateAccountKeys.name, ); }, }; public static readonly ed25519PrivateKey: CommandFlag = { constName: 'ed25519PrivateKey', name: 'ed25519-private-key', definition: { describe: 'Specify a hex-encoded ED25519 private key for the Hedera account', defaultValue: '', type: 'string', dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptPrivateKey( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.ed25519PrivateKey.definition.defaultValue as string, 'Enter the private key: ', undefined, Flags.ed25519PrivateKey.name, ); }, }; public static readonly generateEcdsaKey: CommandFlag = { constName: 'generateEcdsaKey', name: 'generate-ecdsa-key', definition: { describe: 'Generate ECDSA private key for the Hedera account', defaultValue: false, type: 'boolean', }, prompt: undefined, }; public static readonly ecdsaPrivateKey: CommandFlag = { constName: 'ecdsaPrivateKey', name: 'ecdsa-private-key', definition: { describe: 'Specify a hex-encoded ECDSA private key for the Hedera account', defaultValue: '', type: 'string', dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptPrivateKey( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.ed25519PrivateKey.definition.defaultValue as string, 'Enter the private key: ', undefined, Flags.ed25519PrivateKey.name, ); }, }; public static readonly setAlias: CommandFlag = { constName: 'setAlias', name: 'set-alias', definition: { describe: 'Sets the alias for the Hedera account when it is created, requires --ecdsa-private-key', defaultValue: false, type: 'boolean', }, prompt: undefined, }; public static readonly accountId: CommandFlag = { constName: 'accountId', name: 'account-id', definition: { describe: 'The Hedera account id, e.g.: 0.0.1001', defaultValue: '', type: 'string', }, prompt: async function promptAccountId( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input, Flags.accountId.definition.defaultValue as string, 'Enter the account id: ', undefined, Flags.accountId.name, ); }, }; public static readonly fileId: CommandFlag = { constName: 'fileId', name: 'file-id', definition: { describe: 'The network file id, e.g.: 0.0.150', defaultValue: '', type: 'string', }, prompt: async function promptFileId(task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> { return await Flags.promptText( task, input, Flags.fileId.definition.defaultValue as string, 'Enter the file id: ', 'File ID cannot be empty', Flags.fileId.name, ); }, }; public static readonly filePath: CommandFlag = { constName: 'filePath', name: 'file-path', definition: { describe: 'Local path to the file to upload', defaultValue: '', type: 'string', }, prompt: async function promptFilePath(task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> { return await Flags.promptText( task, input, Flags.filePath.definition.defaultValue as string, 'Enter the file path: ', 'File path cannot be empty', Flags.filePath.name, ); }, }; public static readonly amount: CommandFlag = { constName: 'amount', name: 'hbar-amount', definition: { describe: 'Amount of HBAR to add', defaultValue: 100, type: 'number', }, prompt: async function promptAmount(task: SoloListrTaskWrapper<AnyListrContext>, input: number): Promise<number> { return await Flags.prompt( 'number', task, input, Flags.amount.definition.defaultValue, 'How much HBAR do you want to add? ', undefined, Flags.amount.name, ); }, }; public static readonly createAmount: CommandFlag = { constName: 'createAmount', name: 'create-amount', definition: { describe: 'Amount of new account to create', defaultValue: 1, type: 'number', }, prompt: async function promptCreateAmount( task: SoloListrTaskWrapper<AnyListrContext>, input: number, ): Promise<number> { return await Flags.prompt( 'number', task, input, Flags.createAmount.definition.defaultValue, 'How many account to create? ', undefined, Flags.createAmount.name, ); }, }; public static readonly nodeAlias: CommandFlag = { constName: 'nodeAlias', name: 'node-alias', definition: { describe: 'Node alias (e.g. node99)', type: 'string', }, prompt: async function promptNewNodeAlias( task: SoloListrTaskWrapper<AnyListrContext>, input: string, ): Promise<string> { return await Flags.promptText( task, input,