@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
1,263 lines • 90.4 kB
JavaScript
// 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