@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
430 lines • 22 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as constants from '../../core/constants.js';
import chalk from 'chalk';
import { ListrLock } from '../../core/lock/listr-lock.js';
import { ErrorMessages } from '../../core/error-messages.js';
import { SoloError } from '../../core/errors/solo-error.js';
import { UserBreak } from '../../core/errors/user-break.js';
import { ListrInquirerPromptAdapter } from '@listr2/prompt-adapter-inquirer';
import { confirm as confirmPrompt } from '@inquirer/prompts';
import { inject, injectable } from 'tsyringe-neo';
import { patchInject } from '../../core/dependency-injection/container-helper.js';
import { InjectTokens } from '../../core/dependency-injection/inject-tokens.js';
import { LocalConfigRuntimeState } from '../../business/runtime-state/config/local/local-config-runtime-state.js';
import { StringFacade } from '../../business/runtime-state/facade/string-facade.js';
import { RemoteConfigRuntimeState } from '../../business/runtime-state/config/remote/remote-config-runtime-state.js';
import * as versions from '../../../version.js';
import { findMinioOperator } from '../../core/helpers.js';
let ClusterCommandTasks = class ClusterCommandTasks {
k8Factory;
localConfig;
logger;
chartManager;
leaseManager;
clusterChecks;
remoteConfig;
oneShotState;
constructor(k8Factory, localConfig, logger, chartManager, leaseManager, clusterChecks, remoteConfig, oneShotState) {
this.k8Factory = k8Factory;
this.localConfig = localConfig;
this.logger = logger;
this.chartManager = chartManager;
this.leaseManager = leaseManager;
this.clusterChecks = clusterChecks;
this.remoteConfig = remoteConfig;
this.oneShotState = oneShotState;
this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name);
this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name);
this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name);
this.chartManager = patchInject(chartManager, InjectTokens.ChartManager, this.constructor.name);
this.leaseManager = patchInject(leaseManager, InjectTokens.LockManager, this.constructor.name);
this.clusterChecks = patchInject(clusterChecks, InjectTokens.ClusterChecks, this.constructor.name);
this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name);
this.oneShotState = patchInject(oneShotState, InjectTokens.OneShotState, this.constructor.name);
}
findMinioOperator(context) {
return findMinioOperator(context, this.k8Factory);
}
connectClusterRef() {
return {
title: 'Associate a context with a cluster reference: ',
task: async (context_, task) => {
task.title += context_.config.clusterRef;
this.localConfig.configuration.clusterRefs.set(context_.config.clusterRef, new StringFacade(context_.config.context));
await this.localConfig.persist();
},
};
}
disconnectClusterRef() {
return {
title: 'Remove cluster reference ',
task: async (context_, task) => {
task.title += context_.config.clusterRef;
this.localConfig.configuration.clusterRefs.delete(context_.config.clusterRef);
await this.localConfig.persist();
},
};
}
testConnectionToCluster() {
return {
title: 'Test connection to cluster: ',
task: async ({ config: { context, clusterRef } }, task) => {
task.title += context;
try {
await this.k8Factory.getK8(context).namespaces().list();
}
catch {
task.title = `${task.title} - ${chalk.red('Cluster connection failed')}`;
throw new SoloError(ErrorMessages.INVALID_CONTEXT_FOR_CLUSTER_DETAILED(context, clusterRef));
}
},
};
}
validateClusterRefs() {
return {
title: 'Validating cluster ref: ',
task: async ({ config: { clusterRef } }, task) => {
task.title += clusterRef;
if (this.localConfig.configuration.clusterRefs.get(clusterRef)) {
this.logger.showUser(chalk.yellow(`Cluster ref ${clusterRef} already exists inside local config`));
}
},
};
}
/** Show list of installed chart */
async showInstalledChartList(clusterSetupNamespace, context) {
// TODO convert to logger.addMessageGroup() & logger.addMessageGroupMessage()
this.logger.showList('Installed Charts', await this.chartManager.getInstalledCharts(clusterSetupNamespace, context));
}
initialize(argv, configInit, loadRemoteConfig = false) {
const { required, optional } = argv;
argv.flags = [...required, ...optional];
return {
title: 'Initialize',
task: async (context_, task) => {
await this.localConfig.load();
if (loadRemoteConfig) {
await this.remoteConfig.loadAndValidate(argv);
}
context_.config = await configInit(argv, context_, task);
},
};
}
showClusterList() {
return {
title: 'List all available clusters',
task: async () => {
await this.localConfig.load();
const clusterReferences = this.localConfig.configuration.clusterRefs;
const clusterList = [];
for (const [clusterName, clusterContext] of clusterReferences) {
clusterList.push(`${clusterName}:${clusterContext.toString()}`);
}
this.logger.showList('Cluster references and the respective contexts', clusterList);
},
};
}
getClusterInfo() {
return {
title: 'Get cluster info',
task: async (context_, task) => {
const clusterReference = context_.config.clusterRef;
const clusterReferences = this.localConfig.configuration.clusterRefs;
const deployments = this.localConfig.configuration.deployments;
const context = clusterReferences.get(clusterReference);
if (!context) {
throw new Error(`Cluster "${clusterReference}" not found in the LocalConfig`);
}
const deploymentsWithSelectedCluster = [...deployments]
.filter((deployment) => deployment.clusters.some((cluster) => cluster.toString() === clusterReference))
.map((deployment) => ({
name: deployment.name,
namespace: deployment.namespace || 'default',
}));
task.output =
`Cluster Reference: ${clusterReference}\n` +
`Associated Context: ${context}\n` +
'Deployments using this Cluster:';
task.output +=
deploymentsWithSelectedCluster.length > 0
? '\n' +
deploymentsWithSelectedCluster
.map((dep) => ` - ${dep.name} [Namespace: ${dep.namespace}]`)
.join('\n')
: '\n - None';
this.logger.showUser(task.output);
},
};
}
installMinioOperator() {
return {
title: 'Install MinIO Operator chart',
task: async ({ config: { clusterSetupNamespace, context } }) => {
const { exists: isMinioInstalled } = await this.findMinioOperator(context);
if (isMinioInstalled) {
this.logger.showUser(`⏭️ MinIO Operator chart already installed in context ${context}, skipping`);
return;
}
try {
await this.chartManager.install(clusterSetupNamespace, constants.MINIO_OPERATOR_RELEASE_NAME, constants.MINIO_OPERATOR_CHART, constants.MINIO_OPERATOR_CHART, versions.MINIO_OPERATOR_VERSION, '--set operator.replicaCount=1', context);
this.logger.showUser(`✅ MinIO Operator chart installed successfully on context ${context}`);
}
catch (error) {
this.logger.debug('Error installing MinIO Operator chart', error);
try {
await this.chartManager.uninstall(clusterSetupNamespace, constants.MINIO_OPERATOR_RELEASE_NAME, context);
}
catch (uninstallError) {
this.logger.showUserError(uninstallError);
}
throw new SoloError('Error installing MinIO Operator chart', error);
}
},
skip: ({ config: { deployMinio } }) => !deployMinio,
};
}
installPrometheusStack() {
return {
title: 'Install Prometheus Stack chart',
task: async (context_) => {
const clusterSetupNamespace = context_.config.clusterSetupNamespace;
const isPrometheusInstalled = await this.chartManager.isChartInstalled(clusterSetupNamespace, constants.PROMETHEUS_RELEASE_NAME, context_.config.context);
if (isPrometheusInstalled) {
this.logger.showUser('⏭️ Prometheus Stack chart already installed, skipping');
}
else {
try {
await this.chartManager.install(clusterSetupNamespace, constants.PROMETHEUS_RELEASE_NAME, constants.PROMETHEUS_STACK_CHART, constants.PROMETHEUS_STACK_CHART, versions.PROMETHEUS_STACK_VERSION, '', context_.config.context);
this.logger.showUser('✅ Prometheus Stack chart installed successfully');
}
catch (error) {
this.logger.debug('Error installing Prometheus Stack chart', error);
try {
await this.chartManager.uninstall(clusterSetupNamespace, constants.PROMETHEUS_RELEASE_NAME, context_.config.context);
}
catch (uninstallError) {
this.logger.showUserError(uninstallError);
}
throw new SoloError('Error installing Prometheus Stack chart', error);
}
}
},
skip: (context_) => !context_.config.deployPrometheusStack,
};
}
installMetricsServer() {
return {
title: 'Install metrics-server chart',
task: async ({ config: { context } }) => {
const isMetricsServerInstalled = await this.chartManager.isChartInstalled(constants.METRICS_SERVER_NAMESPACE, constants.METRICS_SERVER_RELEASE_NAME, context);
if (isMetricsServerInstalled) {
this.logger.showUser('⏭️ metrics-server chart already installed, skipping');
return;
}
try {
await this.chartManager.install(constants.METRICS_SERVER_NAMESPACE, constants.METRICS_SERVER_RELEASE_NAME, constants.METRICS_SERVER_CHART, constants.METRICS_SERVER_CHART, versions.METRICS_SERVER_VERSION, constants.METRICS_SERVER_INSTALL_ARGS, context);
this.logger.showUser('metrics-server chart installed successfully');
}
catch (error) {
this.logger.debug('Error installing metrics-server chart', error);
try {
await this.chartManager.uninstall(constants.METRICS_SERVER_NAMESPACE, constants.METRICS_SERVER_RELEASE_NAME, context);
}
catch (uninstallError) {
this.logger.showUserError(uninstallError);
}
throw new SoloError('Error installing metrics-server chart', error);
}
},
skip: ({ config: { deployMetricsServer } }) => !deployMetricsServer,
};
}
installPodMonitorRole() {
return {
title: 'Install pod-monitor-role ClusterRole',
task: async (context_) => {
const k8 = this.k8Factory.getK8(context_.config.context);
try {
// Check if ClusterRole already exists using Kubernetes JavaScript API
await k8.rbac().clusterRoleExists(constants.POD_MONITOR_ROLE);
this.logger.showUser(`⏭️ ClusterRole pod-monitor-role already exists in context ${context_.config.context}, skipping`);
}
catch {
// ClusterRole doesn't exist, create it
try {
await k8.rbac().createClusterRole(constants.POD_MONITOR_ROLE, [
{
apiGroups: [''],
resources: ['pods', 'services', 'clusterroles', 'pods/log', 'secrets'],
verbs: ['get', 'list'],
},
{
apiGroups: [''],
resources: ['pods/exec'],
verbs: ['create'],
},
], { 'solo.hedera.com/type': 'cluster-role' });
this.logger.showUser(`✅ ClusterRole pod-monitor-role installed successfully in context ${context_.config.context}`);
}
catch (installError) {
this.logger.debug('Error installing pod-monitor-role ClusterRole', installError);
throw new SoloError('Error installing pod-monitor-role ClusterRole', installError);
}
}
},
};
}
uninstallPodMonitorRole() {
return {
title: 'Uninstall pod-monitor-role ClusterRole',
task: async ({ config: { context } }) => {
try {
// Check if ClusterRole exists using Kubernetes JavaScript API
await this.k8Factory.getK8(context).rbac().clusterRoleExists(constants.POD_MONITOR_ROLE);
// ClusterRole exists, delete it
await this.k8Factory.getK8(context).rbac().deleteClusterRole(constants.POD_MONITOR_ROLE);
this.logger.showUser('✅ ClusterRole pod-monitor-role uninstalled successfully');
}
catch {
// ClusterRole doesn't exist, skip
this.logger.showUser('⏭️ ClusterRole pod-monitor-role not found, skipping');
}
},
};
}
installClusterChart(argv) {
return {
title: 'Install cluster charts',
task: async (context_, task) => {
// switch to the correct cluster context first
const k8 = this.k8Factory.getK8(context_.config.context);
k8.contexts().updateCurrent(context_.config.context);
// Always install pod-monitor-role ClusterRole first
const subtasks = [this.installPodMonitorRole()];
if (context_.config.deployMinio) {
subtasks.push(this.installMinioOperator());
}
if (context_.config.deployPrometheusStack) {
subtasks.push(this.installPrometheusStack());
}
if (context_.config.deployMetricsServer) {
subtasks.push(this.installMetricsServer());
}
const result = await task.newListr(subtasks, { concurrent: false });
if (argv.dev) {
await this.showInstalledChartList(context_.config.clusterSetupNamespace, context_.config.context);
}
return result;
},
};
}
acquireNewLease() {
return {
title: 'Acquire new lease',
task: async (_, task) => {
if (!this.oneShotState.isActive()) {
const lease = await this.leaseManager.create();
return ListrLock.newAcquireLockTask(lease, task);
}
return ListrLock.newSkippedLockTask(task);
},
};
}
uninstallMinioOperator() {
return {
title: 'Uninstall MinIO Operator chart',
task: async ({ config: { clusterSetupNamespace: namespace, context } }) => {
const { exists: isMinioInstalled, releaseName } = await this.findMinioOperator(context);
if (isMinioInstalled) {
await this.chartManager.uninstall(namespace, releaseName, context);
this.logger.showUser('✅ MinIO Operator chart uninstalled successfully');
}
else {
this.logger.showUser('⏭️ MinIO Operator chart not installed, skipping');
}
},
};
}
uninstallPrometheusStack() {
return {
title: 'Uninstall Prometheus Stack chart',
task: async ({ config: { clusterSetupNamespace, context } }) => {
const isPrometheusInstalled = await this.chartManager.isChartInstalled(clusterSetupNamespace, constants.PROMETHEUS_RELEASE_NAME, context);
if (isPrometheusInstalled) {
await this.chartManager.uninstall(clusterSetupNamespace, constants.PROMETHEUS_RELEASE_NAME, context);
this.logger.showUser('✅ Prometheus Stack chart uninstalled successfully');
}
else {
this.logger.showUser('⏭️ Prometheus Stack chart not installed, skipping');
}
},
};
}
uninstallMetricsServer() {
return {
title: 'Uninstall metrics-server chart',
task: async ({ config: { context } }) => {
const isMetricsServerInstalled = await this.chartManager.isChartInstalled(constants.METRICS_SERVER_NAMESPACE, constants.METRICS_SERVER_RELEASE_NAME, context);
if (isMetricsServerInstalled) {
await this.chartManager.uninstall(constants.METRICS_SERVER_NAMESPACE, constants.METRICS_SERVER_RELEASE_NAME, context);
this.logger.showUser('Metrics-server chart uninstalled successfully');
}
else {
this.logger.showUser('Metrics-server chart not installed, skipping');
}
},
};
}
uninstallClusterChart(argv) {
return {
title: 'Uninstall cluster charts',
task: async ({ config: { clusterSetupNamespace, context } }, task) => {
if (!argv.force && (await this.clusterChecks.isRemoteConfigPresentInAnyNamespace(context))) {
const confirm = await task.prompt(ListrInquirerPromptAdapter).run(confirmPrompt, {
default: false,
message: 'There is remote config for one of the deployments' +
'Are you sure you would like to uninstall the cluster?',
});
if (!confirm) {
throw new UserBreak('Aborted application by user prompt');
}
}
if (argv.dev) {
await this.showInstalledChartList(clusterSetupNamespace);
}
return task.newListr([
this.uninstallMetricsServer(),
this.uninstallPrometheusStack(),
this.uninstallMinioOperator(),
this.uninstallPodMonitorRole(),
], { concurrent: false });
},
};
}
};
ClusterCommandTasks = __decorate([
injectable(),
__param(0, inject(InjectTokens.K8Factory)),
__param(1, inject(InjectTokens.LocalConfigRuntimeState)),
__param(2, inject(InjectTokens.SoloLogger)),
__param(3, inject(InjectTokens.ChartManager)),
__param(4, inject(InjectTokens.LockManager)),
__param(5, inject(InjectTokens.ClusterChecks)),
__param(6, inject(InjectTokens.RemoteConfigRuntimeState)),
__param(7, inject(InjectTokens.OneShotState)),
__metadata("design:paramtypes", [Object, LocalConfigRuntimeState, Object, Function, Function, Function, RemoteConfigRuntimeState, Function])
], ClusterCommandTasks);
export { ClusterCommandTasks };
//# sourceMappingURL=tasks.js.map