@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
1,220 lines • 65 kB
JavaScript
/**
* SPDX-License-Identifier: Apache-2.0
*/
import * as constants from '../core/constants.js';
import * as version from '../../version.js';
import path from 'path';
import fs from 'fs';
import { IllegalArgumentError, SoloError } from '../core/errors.js';
import { ListrEnquirerPromptAdapter } from '@listr2/prompt-adapter-enquirer';
import * as helpers from '../core/helpers.js';
import validator from 'validator';
export class Flags {
static KEY_COMMON = '_COMMON_';
static async prompt(type, task, input, 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');
}
input = await task.prompt(ListrEnquirerPromptAdapter).run({
type,
default: defaultValue,
message: promptMessage,
});
}
if (emptyCheckMessage && !input) {
throw new SoloError(emptyCheckMessage);
}
return input;
}
catch (e) {
throw new SoloError(`input failed: ${flagName}: ${e.message}`, e);
}
}
static async promptText(task, input, defaultValue, promptMessage, emptyCheckMessage, flagName) {
return await Flags.prompt('text', 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 setCommandFlags(y, ...commandFlags) {
commandFlags.forEach(flag => {
y.option(flag.name, flag.definition);
});
}
static devMode = {
constName: 'devMode',
name: 'dev',
definition: {
describe: 'Enable developer mode',
defaultValue: false,
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,
};
// 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 promptClusterRef(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);
},
};
/**
* 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(',');
inputItems.forEach(v => {
const parts = v.split('=');
let clusterRef = '';
let valuesFile = '';
if (parts.length !== 2) {
valuesFile = path.resolve(v);
clusterRef = Flags.KEY_COMMON;
}
else {
clusterRef = parts[0];
valuesFile = path.resolve(parts[1]);
}
if (!valuesFiles[clusterRef]) {
valuesFiles[clusterRef] = [];
}
valuesFiles[clusterRef].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(task, 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(task, 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 profileFile = {
constName: 'profileFile',
name: 'profile-file',
definition: {
describe: 'Resource profile definition (e.g. custom-spec.yaml)',
defaultValue: constants.DEFAULT_PROFILE_FILE,
type: 'string',
},
prompt: async function promptProfileFile(task, input) {
if (input && !fs.existsSync(input)) {
input = await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'text',
default: Flags.valuesFile.definition.defaultValue,
message: 'Enter path to custom resource profile definition file: ',
});
}
if (input && !fs.existsSync(input)) {
throw new IllegalArgumentError(`Invalid profile definition file: ${input}}`, input);
}
return input;
},
};
static profileName = {
constName: 'profileName',
name: 'profile',
definition: {
describe: `Resource profile (${constants.ALL_PROFILES.join(' | ')})`,
defaultValue: constants.PROFILE_LOCAL,
type: 'string',
},
prompt: async function promptProfile(task, input, choices = constants.ALL_PROFILES) {
try {
const initial = choices.indexOf(input);
if (initial < 0) {
const input = await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'select',
message: 'Select profile for solo network deployment',
choices: helpers.cloneArray(choices),
});
if (!input) {
throw new SoloError('key-format cannot be empty');
}
return input;
}
return input;
}
catch (e) {
throw new SoloError(`input failed: ${Flags.profileName.name}`, e);
}
},
};
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? ', null, Flags.deployPrometheusStack.name);
},
};
static enablePrometheusSvcMonitor = {
constName: 'enablePrometheusSvcMonitor',
name: 'prometheus-svc-monitor',
definition: {
describe: 'Enable prometheus service monitor for the network nodes',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptEnablePrometheusSvcMonitor(task, input) {
return await Flags.promptToggle(task, input, Flags.enablePrometheusSvcMonitor.definition.defaultValue, 'Would you like to enable the Prometheus service monitor for the network nodes? ', null, Flags.enablePrometheusSvcMonitor.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? ', null, Flags.deployMinio.name);
},
};
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? ', null, 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? ', null, 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 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 promptCacheDir(task, input) {
return await Flags.promptText(task, input, constants.SOLO_CACHE_DIR, 'Enter local cache directory path: ', null, 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): ', null, Flags.nodeAliasesUnparsed.name);
},
};
static force = {
constName: 'force',
name: 'force',
definition: {
describe: 'Force actions even if those can be skipped',
defaultValue: false,
alias: 'f',
type: 'boolean',
},
prompt: async function promptForce(task, input) {
return await Flags.promptToggle(task, input, Flags.force.definition.defaultValue, 'Would you like to force changes? ', null, Flags.force.name);
},
};
static chartDirectory = {
constName: 'chartDirectory',
name: 'chart-dir',
definition: {
describe: 'Local chart directory path (e.g. ~/solo-charts/charts',
defaultValue: '',
alias: 'd',
type: 'string',
},
prompt: async function promptChartDir(task, input) {
try {
if (input === 'false') {
return '';
}
if (input && !fs.existsSync(input)) {
input = await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'text',
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 (e) {
throw new SoloError(`input failed: ${Flags.chartDirectory.name}`, e);
}
},
};
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? ', null, Flags.replicaCount.name);
},
};
static chainId = {
constName: 'chainId',
name: 'ledger-id',
definition: {
describe: 'Ledger ID (a.k.a. Chain ID)',
defaultValue: constants.HEDERA_CHAIN_ID, // Ref: https://github.com/hashgraph/hedera-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: ', null, Flags.chainId.name);
},
};
// Ref: https://github.com/hashgraph/hedera-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: ', null, Flags.operatorId.name);
},
};
// Ref: https://github.com/hashgraph/hedera-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: ', null, 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: ', null, 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} `, null, 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? ', null, 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) {
try {
if (!input) {
input = await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'text',
default: Flags.tlsClusterIssuerType.definition.defaultValue,
message: 'Enter TLS cluster issuer type, available options are: "acme-staging", "acme-prod", or "self-signed":',
});
}
if (!input || !['acme-staging', 'acme-prod', 'self-signed'].includes(input)) {
throw new SoloError('must be one of: "acme-staging", "acme-prod", or "self-signed"');
}
return input;
}
catch (e) {
throw new SoloError(`input failed: ${Flags.tlsClusterIssuerType.name}`, e);
}
},
};
static enableHederaExplorerTls = {
constName: 'enableHederaExplorerTls',
name: 'enable-hedera-explorer-tls',
definition: {
describe: 'Enable the Hedera 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 promptEnableHederaExplorerTls(task, input) {
return await Flags.promptToggle(task, input, Flags.enableHederaExplorerTls.definition.defaultValue, 'Would you like to enable the Hedera Explorer TLS? ', null, Flags.enableHederaExplorerTls.name);
},
};
static hederaExplorerStaticIp = {
constName: 'hederaExplorerStaticIp',
name: 'hedera-explorer-static-ip',
definition: {
describe: 'The static IP address to use for the Hedera Explorer load balancer, defaults to ""',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
static hederaExplorerTlsHostName = {
constName: 'hederaExplorerTlsHostName',
name: 'hedera-explorer-tls-host-name',
definition: {
describe: 'The host name to use for the Hedera Explorer TLS, defaults to "explorer.solo.local"',
defaultValue: 'explorer.solo.local',
type: 'string',
},
prompt: async function promptHederaExplorerTlsHostName(task, input) {
return await Flags.promptText(task, input, Flags.hederaExplorerTlsHostName.definition.defaultValue, 'Enter the host name to use for the Hedera Explorer TLS: ', null, Flags.hederaExplorerTlsHostName.name);
},
};
static deletePvcs = {
constName: 'deletePvcs',
name: 'delete-pvcs',
definition: {
describe: 'Delete the persistent volume claims',
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? ', null, Flags.deletePvcs.name);
},
};
static deleteSecrets = {
constName: 'deleteSecrets',
name: 'delete-secrets',
definition: {
describe: 'Delete the network secrets',
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? ', null, 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: ', null, Flags.soloChartVersion.name);
},
};
static applicationProperties = {
constName: 'applicationProperties',
name: 'application-properties',
definition: {
describe: 'application.properties file for node',
defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', '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: path.join(constants.SOLO_CACHE_DIR, 'templates', 'application.env'),
type: 'string',
},
prompt: undefined,
};
static apiPermissionProperties = {
constName: 'apiPermissionProperties',
name: 'api-permission-properties',
definition: {
describe: 'api-permission.properties file for node',
defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'api-permission.properties'),
type: 'string',
},
prompt: undefined,
};
static bootstrapProperties = {
constName: 'bootstrapProperties',
name: 'bootstrap-properties',
definition: {
describe: 'bootstrap.properties file for node',
defaultValue: path.join(constants.SOLO_CACHE_DIR, '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: path.join(constants.SOLO_CACHE_DIR, '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: '',
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: path.join(constants.SOLO_CACHE_DIR, '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? ', null, Flags.updateAccountKeys.name);
},
};
static ed25519PrivateKey = {
constName: 'ed25519PrivateKey',
name: 'ed25519-private-key',
definition: {
describe: '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: ', null, 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: '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: ', null, 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: ', null, Flags.accountId.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? ', null, 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? ', null, 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: ', null, Flags.nodeAlias.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): ', null, 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): ', null, 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): ', null, Flags.endpointType.name);
},
};
static persistentVolumeClaims = {
constName: 'persistentVolumeClaims',
name: 'pvcs',
definition: {
describe: 'Enable persistent volume claims to store data outside the pod, required for 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? ', null, 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 promptOutputDir(task, input) {
return await Flags.promptToggle(task, input, Flags.outputDir.definition.defaultValue, 'Enter path to directory to store the temporary context file', null, Flags.outputDir.name);
},
};
static inputDir = {
constName: 'inputDir',
name: 'input-dir',
definition: {
describe: 'Path to the directory where the command context will be loaded from',
defaultValue: '',
type: 'string',
},
prompt: async function promptInputDir(task, input) {
return await Flags.promptToggle(task, input, Flags.inputDir.definition.defaultValue, 'Enter path to directory containing the temporary context file', null, Flags.inputDir.name);
},
};
static adminKey = {
constName: 'adminKey',
name: 'admin-key',
definition: {
describe: 'Admin key',
defaultValue: constants.GENESIS_KEY,
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: undefined,
};
static adminPublicKeys = {
constName: 'adminPublicKeys',
name: 'admin-public-keys',
definition: {
describe: 'Comma separated list of DER encoded ED25519 public keys and must match the order of the node aliases',
defaultValue: constants.GENESIS_KEY,
type: 'string',
},
prompt: undefined,
};
static quiet = {
constName: 'quiet',
name: 'quiet-mode',
definition: {
describe: 'Quiet mode, do not prompt for confirmation',
defaultValue: false,
alias: 'q',
type: 'boolean',
disablePrompt: true,
},
prompt: undefined,
};
static mirrorNodeVersion = {
constName: 'mirrorNodeVersion',
name: 'mirror-node-version',
definition: {
describe: 'Mirror node chart version',
defaultValue: version.MIRROR_NODE_VERSION,
type: 'string',
},
prompt: async function promptMirrorNodeVersion(task, input) {
return await Flags.promptToggle(task, input, Flags.mirrorNodeVersion.definition.defaultValue, 'Would you like to choose mirror node version? ', null, Flags.mirrorNodeVersion.name);
},
};
static enableIngress = {
constName: 'enableIngress',
name: 'enable-ingress',
definition: {
describe: 'enable ingress on the component/pod',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
static mirrorStaticIp = {
constName: 'mirrorStaticIp',
name: 'mirror-static-ip',
definition: {
describe: 'static IP address for the mirror node',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
static hederaExplorerVersion = {
constName: 'hederaExplorerVersion',
name: 'hedera-explorer-version',
definition: {
describe: 'Hedera explorer chart version',
defaultValue: version.HEDERA_EXPLORER_VERSION,
type: 'string',
},
prompt: async function promptHederaExplorerVersion(task, input) {
return await Flags.promptToggle(task, input, Flags.hederaExplorerVersion.definition.defaultValue, 'Would you like to choose hedera explorer version? ', null, Flags.hederaExplorerVersion.name);
},
};
static userEmailAddress = {
constName: 'userEmailAddress',
name: 'email',
definition: {
describe: 'User email address used for local configuration',
type: 'string',
},
prompt: async function promptUserEmailAddress(task, input) {
if (input?.length) {
return input;
}
const promptForInput = async () => {
return await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'text',
message: 'Please enter your email address:',
});
};
input = await promptForInput();
while (!validator.isEmail(input)) {
input = await promptForInput();
}
return input;
},
};
static context = {
constName: 'contextName',
name: 'context',
definition: {
describe: 'The Kubernetes context name to be used. Multiple contexts can be separated by a comma',
defaultValue: '',
type: 'string',
},
prompt: async function promptContext(task, input, cluster) {
return await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'select',
name: 'context',
message: 'Select kubectl context' + (cluster ? ` to be associated with cluster: ${cluster}` : ''),
choices: input,
});
},
};
static deployment = {
constName: 'deployment',
name: 'deployment',
definition: {
describe: 'The name the user will reference locally to link to a deployment',
defaultValue: '',
type: 'string',
},
prompt: async function promptDeployment(task, input) {
return await Flags.promptText(task, input, Flags.deployment.definition.defaultValue, 'Enter the name of the deployment:', null, Flags.deployment.name);
},
};
static deploymentClusters = {
constName: 'deploymentClusters',
name: 'deployment-clusters',
definition: {
describe: 'Solo deployment cluster list (comma separated)',
type: 'string',
},
prompt: async function promptDeploymentClusters(task, input) {
return await Flags.promptText(task, input, Flags.deploymentClusters.definition.defaultValue, 'Enter the Solo deployment cluster names (comma separated): ', null, Flags.deploymentClusters.name);
},
};
static pinger = {
constName: 'pinger',
name: 'pinger',
definition: {
describe: 'Enable Pinger service in the Mirror node monitor',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
//* ------------- Node Proxy Certificates ------------- !//
static grpcTlsCertificatePath = {
constName: 'grpcTlsCertificatePath',
name: 'grpc-tls-cert',
definition: {
describe: 'TLS Certificate path for the gRPC ' +
'(e.g. "node1=/Users/username/node1-grpc.cert" ' +
'with multiple nodes comma seperated)',
defaultValue: '',
type: 'string',
},
prompt: async function promptGrpcTlsCertificatePath(task, input) {
return await Flags.promptText(task, input, Flags.grpcTlsCertificatePath.definition.defaultValue, 'Enter node alias and path to TLS certificate for gRPC (ex. nodeAlias=path )', null, Flags.grpcTlsCertificatePath.name);
},
};
static grpcWebTlsCertificatePath = {
constName: 'grpcWebTlsCertificatePath',
name: 'grpc-web-tls-cert',
definition: {
describe: 'TLS Certificate path for gRPC Web ' +
'(e.g. "node1=/Users/username/node1-grpc-web.cert" ' +
'with multiple nodes comma seperated)',
defaultValue: '',
type: 'string',
},
prompt: async function promptGrpcWebTlsCertificatePath(task, input) {
return await Flags.promptText(task, input, Flags.grpcWebTlsCertificatePath.definition.defaultValue, 'Enter node alias and path to TLS certificate for gGRPC web (ex. nodeAlias=path )', null, Flags.grpcWebTlsCertificatePath.name);
},
};
static useExternalDatabase = {
constName: 'useExternalDatabase',
name: 'use-external-database',
definition: {
describe: 'Set to true if you have an external database to use instead of the database that the Mirror Node Helm chart supplies',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
//* ----------------- External Mirror Node PostgreSQL Database Related Flags ------------------ *//
static externalDatabaseHost = {
constName: 'externalDatabaseHost',
name: 'external-database-host',
definition: {
describe: `Use to provide the external database host if the '--${Flags.useExternalDatabase.name}' is passed`,
defaultValue: '',
type: 'string',
},
prompt: async function promptGrpcWebTlsKeyPath(task, input) {
return await Flags.promptText(task, input, Flags.externalDatabaseHost.definition.defaultValue, 'Enter host of the external database', null, Flags.externalDatabaseHost.name);
},
};
static externalDatabaseOwnerUsername = {
constName: 'externalDatabaseOwnerUsername',
name: 'external-database-owner-username',
definition: {
describe: `Use to provide the external database owner's username if the '--${Flags.useExternalDatabase.name}' is passed`,
defaultValue: '',
type: 'string',
},
prompt: async function promptGrpcWebTlsKeyPath(task, input) {
return await Flags.promptText(task, input, Flags.externalDatabaseOwnerUsername.definition.defaultValue, 'Enter username of the external database owner', null, Flags.externalDatabaseOwnerUsername.name);
},
};
static externalDatabaseOwnerPassword = {
constName: 'externalDatabaseOwnerPassword',
name: 'external-database-owner-password',
definition: {
describe: `Use to provide the external database owner's password if the '--${Flags.useExternalDatabase.name}' is passed`,
defaultValue: '',
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: async function promptGrpcWebTlsKeyPath(task, input) {
return await Flags.promptText(task, input, Flags.externalDatabaseOwnerPassword.definition.defaultValue, 'Enter password of the external database owner', null, Flags.externalDatabaseOwnerPassword.name);
},
};
static externalDatabaseReadonlyUsername = {
constName: 'externalDatabaseReadonlyUsername',
name: 'external-database-read-username',
definition: {
describe: `Use to provide the external database readonly user's username if the '--${Flags.useExternalDatabase.name}' is passed`,
defaultValue: '',
type: 'string',
},
prompt: async function promptGrpcWebTlsKeyPath(task, input) {
return await Flags.promptText(task, input, Flags.externalDatabaseReadonlyUsername.definition.defaultValue, 'Enter username of the external database readonly user', null, Flags.externalDatabaseReadonlyUsername.name);
},
};
static externalDatabaseReadonlyPassword = {
constName: 'externalDatabaseReadonlyPassword',
name: 'external-database-read-password',
definition: {
describe: `Use to provide the external database readonly user's password if the '--${Flags.useExternalDatabase.name}' is passed`,
defaultValue: '',
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: async function promptGrpcWebTlsKeyPath(task, input) {
return await Flags.promptText(task, inpu