UNPKG

@hashgraph/solo

Version:

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

430 lines 22 kB
// 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