UNPKG

@hashgraph/solo

Version:

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

1,263 lines 90.4 kB
// SPDX-License-Identifier: Apache-2.0 import * as constants from '../core/constants.js'; import * as version from '../../version.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 { PathEx } from '../business/utils/path-ex.js'; import validator from 'validator'; export class Flags { static KEY_COMMON = '_COMMON_'; static async prompt(type, task, // eslint-disable-next-line @typescript-eslint/no-explicit-any input, // eslint-disable-next-line @typescript-eslint/no-explicit-any defaultValue, promptMessage, emptyCheckMessage, flagName) { try { let needsPrompt = 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); } } static async promptText(task, input, defaultValue, promptMessage, emptyCheckMessage, flagName) { return await Flags.prompt('input', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName); } static async promptToggle(task, input, defaultValue, promptMessage, emptyCheckMessage, flagName) { 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 */ static disablePrompts(flags) { 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 * */ static setRequiredCommandFlags(y, ...commandFlags) { 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 * */ static setOptionalCommandFlags(y, ...commandFlags) { for (const flag of commandFlags) { const defaultValue = flag.definition.defaultValue === '' ? undefined : flag.definition.defaultValue; y.option(flag.name, { ...flag.definition, default: defaultValue, }); } } static devMode = { constName: 'devMode', name: 'dev', definition: { describe: 'Enable developer mode', defaultValue: constants.SOLO_DEV_OUTPUT, type: 'boolean', }, prompt: undefined, }; static predefinedAccounts = { constName: 'predefinedAccounts', name: 'predefined-accounts', definition: { describe: 'Create predefined accounts on network creation', defaultValue: true, type: 'boolean', }, prompt: undefined, }; static forcePortForward = { 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, }; static externalAddress = { 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. static clusterRef = { 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, input) { return await Flags.promptText(task, input, Flags.clusterRef.definition.defaultValue, 'Enter cluster reference: ', 'cluster reference cannot be empty', Flags.clusterRef.name); }, }; static clusterSetupNamespace = { 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, input) { return await Flags.promptText(task, input, 'solo-cluster', 'Enter cluster setup namespace name: ', 'cluster setup namespace cannot be empty', Flags.clusterSetupNamespace.name); }, }; static namespace = { constName: 'namespace', name: 'namespace', definition: { describe: 'Namespace', alias: 'n', type: 'string', }, prompt: async function promptNamespace(task, input) { return await Flags.promptText(task, input, 'solo', 'Enter namespace name: ', 'namespace cannot be empty', Flags.namespace.name); }, }; static mirrorNamespace = { 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 */ static parseValuesFilesInput(input) { const valuesFiles = {}; if (input) { const inputItems = input.split(','); for (const v of inputItems) { const parts = v.split('='); let clusterReference; let valuesFile; 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; } static valuesFile = { constName: 'valuesFile', name: 'values-file', definition: { describe: 'Comma separated chart values file', defaultValue: '', alias: 'f', type: 'string', }, prompt: async function promptValuesFile(_, input) { return input; // no prompt is needed for values file }, }; static networkDeploymentValuesFile = { 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(_, input) { if (input) { Flags.parseValuesFilesInput(input); // validate input as early as possible by parsing it } return input; // no prompt is needed for values file }, }; static deployPrometheusStack = { constName: 'deployPrometheusStack', name: 'prometheus-stack', definition: { describe: 'Deploy prometheus stack', defaultValue: false, type: 'boolean', }, prompt: async function promptDeployPrometheusStack(task, input) { return await Flags.promptToggle(task, input, Flags.deployPrometheusStack.definition.defaultValue, 'Would you like to deploy prometheus stack? ', undefined, Flags.deployPrometheusStack.name); }, }; static deployMinio = { constName: 'deployMinio', name: 'minio', definition: { describe: 'Deploy minio operator', defaultValue: true, type: 'boolean', }, prompt: async function promptDeployMinio(task, input) { return await Flags.promptToggle(task, input, Flags.deployMinio.definition.defaultValue, 'Would you like to deploy MinIO? ', undefined, Flags.deployMinio.name); }, }; static deployMetricsServer = { 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, }; static deployCertManager = { constName: 'deployCertManager', name: 'cert-manager', definition: { describe: 'Deploy cert manager, also deploys acme-cluster-issuer', defaultValue: false, type: 'boolean', }, prompt: async function promptDeployCertManager(task, input) { return await Flags.promptToggle(task, input, Flags.deployCertManager.definition.defaultValue, '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. */ static deployCertManagerCrds = { constName: 'deployCertManagerCrds', name: 'cert-manager-crds', definition: { describe: 'Deploy cert manager CRDs', defaultValue: false, type: 'boolean', }, prompt: async function promptDeployCertManagerCrds(task, input) { return await Flags.promptToggle(task, input, Flags.deployCertManagerCrds.definition.defaultValue, 'Would you like to deploy Cert Manager CRDs? ', undefined, Flags.deployCertManagerCrds.name); }, }; static deployJsonRpcRelay = { constName: 'deployJsonRpcRelay', name: 'json-rpc-relay', definition: { describe: 'Deploy JSON RPC Relay', defaultValue: false, alias: 'j', type: 'boolean', }, prompt: undefined, }; static stateFile = { constName: 'stateFile', name: 'state-file', definition: { describe: 'A zipped state file to be used for the network', defaultValue: '', type: 'string', }, prompt: undefined, }; static upgradeZipFile = { constName: 'upgradeZipFile', name: 'upgrade-zip-file', definition: { describe: 'A zipped file used for network upgrade', defaultValue: '', type: 'string', }, prompt: undefined, }; static releaseTag = { 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, input) { return await Flags.promptText(task, input, version.HEDERA_PLATFORM_VERSION, 'Enter release version: ', undefined, Flags.releaseTag.name); }, }; static upgradeVersion = { constName: 'upgradeVersion', name: 'upgrade-version', definition: { describe: 'Version to be used for the upgrade', defaultValue: '', type: 'string', }, prompt: undefined, }; static imageTag = { constName: 'imageTag', name: 'image-tag', definition: { describe: 'The Docker image tag to override what is in the Helm Chart', defaultValue: '', type: 'string', }, prompt: undefined, }; static componentImage = { 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, }; static relayReleaseTag = { 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, input) { return await Flags.promptText(task, input, Flags.relayReleaseTag.definition.defaultValue, 'Enter relay release version: ', 'relay-release-tag cannot be empty', Flags.relayReleaseTag.name); }, }; static cacheDir = { constName: 'cacheDir', name: 'cache-dir', definition: { describe: 'Local cache directory', defaultValue: constants.SOLO_CACHE_DIR, type: 'string', }, prompt: async function promptCacheDirectory(task, input) { return await Flags.promptText(task, input, constants.SOLO_CACHE_DIR, 'Enter local cache directory path: ', undefined, Flags.cacheDir.name); }, }; static nodeAliasesUnparsed = { constName: 'nodeAliasesUnparsed', name: 'node-aliases', definition: { describe: 'Comma separated node aliases (empty means all nodes)', alias: 'i', type: 'string', }, prompt: async function promptNodeAliases(task, input) { return await Flags.prompt('input', task, input, 'node1,node2,node3', 'Enter list of node IDs (comma separated list): ', undefined, Flags.nodeAliasesUnparsed.name); }, }; static force = { constName: 'force', name: 'force', definition: { describe: 'Force actions even if those can be skipped', defaultValue: false, type: 'boolean', }, prompt: async function promptForce(task, input) { return await Flags.promptToggle(task, input, Flags.force.definition.defaultValue, 'Would you like to force changes? ', undefined, Flags.force.name); }, }; static forceBlockNodeIntegration = { 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, }; static javaFlightRecorderConfiguration = { constName: 'javaFlightRecorderConfiguration', name: 'jfr-config', definition: { describe: 'Java Flight Recorder configuration file path', defaultValue: '', type: 'string', }, prompt: undefined, }; static chartDirectory = { constName: 'chartDirectory', name: 'chart-dir', definition: { describe: 'Local chart directory path (e.g. ~/solo-charts/charts)', defaultValue: '', type: 'string', }, prompt: async function promptChartDirectory(task, input) { if (input === 'false') { return ''; } try { if (input && !fs.existsSync(input)) { input = await task.prompt(ListrInquirerPromptAdapter).run(inputPrompt, { default: Flags.chartDirectory.definition.defaultValue, 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); } }, }; static relayChartDirectory = { 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, }; static explorerChartDirectory = { 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, }; static blockNodeChartDirectory = { 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, }; static blockNodeTssOverlay = { 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, }; static blockNodeMapping = { constName: 'blockNodeIds', name: 'block-node-mapping', definition: { describe: Flags.renderBlockNodeMappingDescription('block-node'), type: 'string', }, prompt: undefined, }; static externalBlockNodeMapping = { constName: 'externalBlockNodeIds', name: 'external-block-node-mapping', definition: { describe: Flags.renderBlockNodeMappingDescription('external-block-node'), type: 'string', }, prompt: undefined, }; static renderBlockNodeMappingDescription(name) { 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`); } static mirrorNodeChartDirectory = { 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, }; static replicaCount = { constName: 'replicaCount', name: 'replica-count', definition: { describe: 'Replica count', defaultValue: 1, alias: '', type: 'number', }, prompt: async function promptReplicaCount(task, input) { return await Flags.prompt('number', task, input, Flags.replicaCount.definition.defaultValue, 'How many replica do you want? ', undefined, Flags.replicaCount.name); }, }; static id = { constName: 'id', name: 'id', definition: { describe: 'The numeric identifier for the component', type: 'number', }, prompt: async function (task, input) { return await Flags.prompt('number', task, input, undefined, 'Enter component id: ', undefined, Flags.id.name); }, }; static grpcWebEndpoints = { 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, }; static grpcWebEndpoint = { 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, }; static mirrorNodeId = { constName: 'mirrorNodeId', name: 'mirror-node-id', definition: { describe: 'The id of the mirror node which to connect', type: 'number', }, prompt: async function (task, input) { return await Flags.prompt('number', task, input, undefined, 'Enter mirror node id: ', undefined, Flags.mirrorNodeId.name); }, }; static chainId = { 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, input) { return await Flags.promptText(task, input, Flags.chainId.definition.defaultValue, 'Enter chain ID: ', undefined, Flags.chainId.name); }, }; // Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/configuration.md static operatorId = { constName: 'operatorId', name: 'operator-id', definition: { describe: 'Operator ID', defaultValue: undefined, type: 'string', }, prompt: async function promptOperatorId(task, input) { return await Flags.promptText(task, input, Flags.operatorId.definition.defaultValue, 'Enter operator ID: ', undefined, Flags.operatorId.name); }, }; // Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/configuration.md static operatorKey = { constName: 'operatorKey', name: 'operator-key', definition: { describe: 'Operator Key', defaultValue: undefined, type: 'string', dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptOperatorKey(task, input) { return await Flags.promptText(task, input, Flags.operatorKey.definition.defaultValue, 'Enter operator private key: ', undefined, Flags.operatorKey.name); }, }; static privateKey = { constName: 'privateKey', name: 'private-key', definition: { describe: 'Show private key information', defaultValue: false, type: 'boolean', dataMask: constants.STANDARD_DATAMASK, }, prompt: async function promptPrivateKey(task, input) { return await Flags.promptText(task, input, Flags.ed25519PrivateKey.definition.defaultValue, 'Enter the private key: ', undefined, Flags.ed25519PrivateKey.name); }, }; static generateGossipKeys = { constName: 'generateGossipKeys', name: 'gossip-keys', definition: { describe: 'Generate gossip keys for nodes', defaultValue: false, type: 'boolean', }, prompt: async function promptGenerateGossipKeys(task, input) { return await Flags.promptToggle(task, input, Flags.generateGossipKeys.definition.defaultValue, `Would you like to generate Gossip keys? ${typeof input} ${input} `, undefined, Flags.generateGossipKeys.name); }, }; static generateTlsKeys = { constName: 'generateTlsKeys', name: 'tls-keys', definition: { describe: 'Generate gRPC TLS keys for nodes', defaultValue: false, type: 'boolean', }, prompt: async function promptGenerateTLSKeys(task, input) { return await Flags.promptToggle(task, input, Flags.generateTlsKeys.definition.defaultValue, 'Would you like to generate TLS keys? ', undefined, Flags.generateTlsKeys.name); }, }; static enableTimeout = { constName: 'enableTimeout', name: 'enable-timeout', definition: { describe: 'enable time out for running a command', defaultValue: false, type: 'boolean', }, prompt: undefined, }; static tlsClusterIssuerType = { 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, input) { if (input) { return; } try { input = (await task.prompt(ListrInquirerPromptAdapter).run(selectPrompt, { default: Flags.tlsClusterIssuerType.definition.defaultValue, message: 'Enter TLS cluster issuer type, available options are: "acme-staging", "acme-prod", or "self-signed":', choices: ['acme-staging', 'acme-prod', 'self-signed'], })); return input; } catch (error) { throw new SoloError(`input failed: ${Flags.tlsClusterIssuerType.name}`, error); } }, }; static enableExplorerTls = { 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, input) { return await Flags.promptToggle(task, input, Flags.enableExplorerTls.definition.defaultValue, 'Would you like to enable the Explorer TLS? ', undefined, Flags.enableExplorerTls.name); }, }; static ingressControllerValueFile = { constName: 'ingressControllerValueFile', name: 'ingress-controller-value-file', definition: { describe: 'The value file to use for ingress controller, defaults to ""', defaultValue: '', type: 'string', }, prompt: undefined, }; static explorerStaticIp = { 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, }; static explorerTlsHostName = { 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, input) { return await Flags.promptText(task, input, Flags.explorerTlsHostName.definition.defaultValue, 'Enter the host name to use for the Explorer TLS: ', undefined, Flags.explorerTlsHostName.name); }, }; static enableMonitoringSupport = { constName: 'enableMonitoringSupport', name: 'enable-monitoring-support', definition: { describe: 'Enables CRDs for Prometheus and Grafana.', defaultValue: true, type: 'boolean', }, prompt: undefined, }; static deletePvcs = { 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, input) { return await Flags.promptToggle(task, input, Flags.deletePvcs.definition.defaultValue, 'Would you like to delete persistent volume claims upon uninstall? ', undefined, Flags.deletePvcs.name); }, }; static deleteSecrets = { 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, input) { return await Flags.promptToggle(task, input, Flags.deleteSecrets.definition.defaultValue, 'Would you like to delete secrets upon uninstall? ', undefined, Flags.deleteSecrets.name); }, }; static soloChartVersion = { constName: 'soloChartVersion', name: 'solo-chart-version', definition: { describe: 'Solo testing chart version', defaultValue: version.SOLO_CHART_VERSION, type: 'string', }, prompt: async function promptSoloChartVersion(task, input) { return await Flags.promptText(task, input, Flags.soloChartVersion.definition.defaultValue, 'Enter solo testing chart version: ', undefined, Flags.soloChartVersion.name); }, }; static blockNodeChartVersion = { constName: 'chartVersion', name: 'chart-version', definition: { describe: 'Block nodes chart version', defaultValue: version.BLOCK_NODE_VERSION, type: 'string', }, prompt: undefined, }; static priorityMapping = { 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, }; static externalBlockNodeAddress = { 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, }; static wrapsEnabled = { constName: 'wrapsEnabled', name: 'wraps', definition: { describe: 'Enable recursive WRAPs aggregation for hinTS/TSS (CN >= v0.72).', type: 'boolean', defaultValue: false, }, prompt: undefined, }; static wrapsKeyPath = { 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, }; static tssEnabled = { constName: 'tssEnabled', name: 'tss', definition: { describe: 'Enable hinTS/TSS (CN >= v0.72).', type: 'boolean', defaultValue: true, }, prompt: undefined, }; static applicationProperties = { constName: 'applicationProperties', name: 'application-properties', definition: { describe: 'application.properties file for node', defaultValue: PathEx.join('templates', constants.APPLICATION_PROPERTIES), type: 'string', }, prompt: undefined, }; static applicationEnv = { 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, }; static apiPermissionProperties = { 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, }; static bootstrapProperties = { constName: 'bootstrapProperties', name: 'bootstrap-properties', definition: { describe: 'bootstrap.properties file for node', defaultValue: PathEx.join('templates', 'bootstrap.properties'), type: 'string', }, prompt: undefined, }; static genesisThrottlesFile = { constName: 'genesisThrottlesFile', name: 'genesis-throttles-file', definition: { describe: 'throttles.json file used during network genesis', defaultValue: '', type: 'string', }, prompt: undefined, }; static settingTxt = { constName: 'settingTxt', name: 'settings-txt', definition: { describe: 'settings.txt file for node', defaultValue: PathEx.join('templates', 'settings.txt'), type: 'string', }, prompt: undefined, }; static app = { constName: 'app', name: 'app', definition: { describe: 'Testing app name', defaultValue: constants.HEDERA_APP_NAME, type: 'string', }, prompt: undefined, }; static appConfig = { constName: 'appConfig', name: 'app-config', definition: { describe: 'json config file of testing app', defaultValue: '', type: 'string', }, prompt: undefined, }; static localBuildPath = { constName: 'localBuildPath', name: 'local-build-path', definition: { describe: 'path of hedera local repo', defaultValue: constants.getEnvironmentVariable('SOLO_LOCAL_BUILD_PATH') || '', type: 'string', }, prompt: undefined, }; static newAccountNumber = { constName: 'newAccountNumber', name: 'new-account-number', definition: { describe: 'new account number for node update transaction', defaultValue: '', type: 'string', }, prompt: undefined, }; static newAdminKey = { constName: 'newAdminKey', name: 'new-admin-key', definition: { describe: 'new admin key for the Hedera account', defaultValue: '', type: 'string', }, prompt: undefined, }; static gossipPublicKey = { 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, }; static gossipPrivateKey = { 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, }; static tlsPublicKey = { 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, }; static tlsPrivateKey = { 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, }; static log4j2Xml = { constName: 'log4j2Xml', name: 'log4j2-xml', definition: { describe: 'log4j2.xml file for node', defaultValue: PathEx.join('templates', 'log4j2.xml'), type: 'string', }, prompt: undefined, }; static updateAccountKeys = { 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, input) { return await Flags.promptToggle(task, input, Flags.updateAccountKeys.definition.defaultValue, '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); }, }; static ed25519PrivateKey = { 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, input) { return await Flags.promptText(task, input, Flags.ed25519PrivateKey.definition.defaultValue, 'Enter the private key: ', undefined, Flags.ed25519PrivateKey.name); }, }; static generateEcdsaKey = { constName: 'generateEcdsaKey', name: 'generate-ecdsa-key', definition: { describe: 'Generate ECDSA private key for the Hedera account', defaultValue: false, type: 'boolean', }, prompt: undefined, }; static ecdsaPrivateKey = { 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, input) { return await Flags.promptText(task, input, Flags.ed25519PrivateKey.definition.defaultValue, 'Enter the private key: ', undefined, Flags.ed25519PrivateKey.name); }, }; static setAlias = { 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, }; static accountId = { constName: 'accountId', name: 'account-id', definition: { describe: 'The Hedera account id, e.g.: 0.0.1001', defaultValue: '', type: 'string', }, prompt: async function promptAccountId(task, input) { return await Flags.promptText(task, input, Flags.accountId.definition.defaultValue, 'Enter the account id: ', undefined, Flags.accountId.name); }, }; static fileId = { constName: 'fileId', name: 'file-id', definition: { describe: 'The network file id, e.g.: 0.0.150', defaultValue: '', type: 'string', }, prompt: async function promptFileId(task, input) { return await Flags.promptText(task, input, Flags.fileId.definition.defaultValue, 'Enter the file id: ', 'File ID cannot be empty', Flags.fileId.name); }, }; static filePath = { constName: 'filePath', name: 'file-path', definition: { describe: 'Local path to the file to upload', defaultValue: '', type: 'string', }, prompt: async function promptFilePath(task, input) { return await Flags.promptText(task, input, Flags.filePath.definition.defaultValue, 'Enter the file path: ', 'File path cannot be empty', Flags.filePath.name); }, }; static amount = { constName: 'amount', name: 'hbar-amount', definition: { describe: 'Amount of HBAR to add', defaultValue: 100, type: 'number', }, prompt: async function promptAmount(task, input) { return await Flags.prompt('number', task, input, Flags.amount.definition.defaultValue, 'How much HBAR do you want to add? ', undefined, Flags.amount.name); }, }; static createAmount = { constName: 'createAmount', name: 'create-amount', definition: { describe: 'Amount of new account to create', defaultValue: 1, type: 'number', }, prompt: async function promptCreateAmount(task, input) { return await Flags.prompt('number', task, input, Flags.createAmount.definition.defaultValue, 'How many account to create? ', undefined, Flags.createAmount.name); }, }; static nodeAlias = { constName: 'nodeAlias', name: 'node-alias', definition: { describe: 'Node alias (e.g. node99)', type: 'string', }, prompt: async function promptNewNodeAlias(task, input) { return await Flags.promptText(task, input, Flags.nodeAlias.definition.defaultValue, 'Enter the new node id: ', undefined, Flags.nodeAlias.name); }, }; static skipNodeAlias = { constName: 'skipNodeAlias', name: 'skip-node-alias', definition: { describe: 'The node alias to skip, because of a NodeUpdateTransaction or it is down (e.g. node99)', type: 'string', }, prompt: async function promptNewNodeAlias(task, input) { return await Flags.promptText(task, input, Flags.skipNodeAlias.definition.defaultValue, 'Enter the node alias to skip: ', undefined, Flags.skipNodeAlias.name); }, }; static gossipEndpoints = { constName: 'gossipEndpoints', name: 'gossip-endpoints', definition: { describe: 'Comma separated gossip endpoints of the node(e.g. first one is internal, second one is external)', type: 'string', }, prompt: async function promptGossipEndpoints(task, input) { return await Flags.promptText(task, input, Flags.gossipEndpoints.definition.defaultValue, 'Enter the gossip endpoints(comma separated): ', undefined, Flags.gossipEndpoints.name); }, }; static grpcEndpoints = { constName: 'grpcEndpoints', name: 'grpc-endpoints', definition: { describe: 'Comma separated gRPC endpoints of the node (at most 8)', type: 'string', }, prompt: async function promptGrpcEndpoints(task, input) { return await Flags.promptText(task, input, Flags.grpcEndpoints.definition.defaultValue, 'Enter the gRPC endpoints(comma separated): ', undefined, Flags.grpcEndpoints.name); }, }; static endpointType = { constName: 'endpointType', name: 'endpoint-type', definition: { describe: 'Endpoint type (IP or FQDN)', defaultValue: constants.ENDPOINT_TYPE_FQDN, type: 'string', }, prompt: async function promptEndpointType(task, input) { return await Flags.promptText(task, input, Flags.endpointType.definition.defaultValue, 'Enter the endpoint type(IP or FQDN): ', undefined, Flags.endpointType.name); }, }; static persistentVolumeClaims = { constName: 'persistentVolumeClaims', name: 'pvcs', definition: { describe: 'Enable persistent volume claims to store data outside the pod, required for consensus node add', defaultValue: false, type: 'boolean', }, prompt: async function promptPersistentVolumeClaims(task, input) { return await Flags.promptToggle(task, input, Flags.persistentVolumeClaims.definition.defaultValue, 'Would you like to enable persistent volume claims to store data outside the pod? ', undefined, Flags.persistentVolumeClaims.name); }, }; static debugNodeAlias = { constName: 'debugNodeAlias', name: 'debug-node-alias', definition: { describe: 'Enable default jvm debug port (5005) for the given node id', defaultValue: '', type: 'string', }, prompt: undefined, }; static outputDir = { constName: 'outputDir', name: 'output-dir', definition: { describe: 'Path to the directory where the command context will be saved to', defaultValue: '', type: 'string', }, prompt: async function promptOutputDirectory(task, input) { return await Flags.promptToggle(task, input, Flags.outputDir.definition.defaultValue, 'Enter path to directory to store the temporary context file', undefined, Flags.outputDir.name); }, }; static zipPassword = { constName: 'zipPassword', name: 'zip-password', definition: { describe: 'Password to encrypt generated backup ZIP archives', defaultValue: '', type: 'string', }, prompt: undefined, }; static zipFile = { constNa