@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
689 lines • 35.7 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); }
};
var ExplorerCommand_1;
import { ListrInquirerPromptAdapter } from '@listr2/prompt-adapter-inquirer';
import { confirm as confirmPrompt } from '@inquirer/prompts';
import { SoloError } from '../core/errors/solo-error.js';
import { UserBreak } from '../core/errors/user-break.js';
import * as constants from '../core/constants.js';
import { BaseCommand } from './base.js';
import { Flags as flags } from './flags.js';
import { ListrLock } from '../core/lock/listr-lock.js';
import * as helpers from '../core/helpers.js';
import { prepareValuesFiles, showVersionBanner, sleep } from '../core/helpers.js';
import { NamespaceName } from '../types/namespace/namespace-name.js';
import { inject, injectable } from 'tsyringe-neo';
import { InjectTokens } from '../core/dependency-injection/inject-tokens.js';
import { KeyManager } from '../core/key-manager.js';
import { INGRESS_CONTROLLER_VERSION } from '../../version.js';
import { patchInject } from '../core/dependency-injection/container-helper.js';
import { ComponentTypes } from '../core/config/remote/enumerations/component-types.js';
import { Templates } from '../core/templates.js';
import { SemanticVersion } from '../business/utils/semantic-version.js';
import { assertUpgradeVersionNotOlder } from '../core/upgrade-version-guard.js';
import { Duration } from '../core/time/duration.js';
import { createHash } from 'node:crypto';
import { DeploymentPhase } from '../data/schema/model/remote/deployment-phase.js';
import { optionFromFlag } from './command-helpers.js';
var ExplorerCommandType;
(function (ExplorerCommandType) {
ExplorerCommandType["ADD"] = "add";
ExplorerCommandType["UPGRADE"] = "upgrade";
ExplorerCommandType["DESTROY"] = "destroy";
})(ExplorerCommandType || (ExplorerCommandType = {}));
let ExplorerCommand = class ExplorerCommand extends BaseCommand {
static { ExplorerCommand_1 = this; }
clusterChecks;
constructor(clusterChecks) {
super();
this.clusterChecks = clusterChecks;
this.clusterChecks = patchInject(clusterChecks, InjectTokens.ClusterChecks, this.constructor.name);
}
static DEPLOY_CONFIGS_NAME = 'deployConfigs';
static UPGRADE_CONFIGS_NAME = 'upgradeConfigs';
static DEPLOY_FLAGS_LIST = {
required: [flags.deployment],
optional: [
flags.cacheDir,
flags.chartDirectory,
flags.explorerChartDirectory,
flags.clusterRef,
flags.enableIngress,
flags.ingressControllerValueFile,
flags.enableExplorerTls,
flags.explorerTlsHostName,
flags.explorerStaticIp,
flags.explorerVersion,
flags.namespace,
flags.quiet,
flags.soloChartVersion,
flags.tlsClusterIssuerType,
flags.valuesFile,
flags.clusterSetupNamespace,
flags.domainName,
flags.forcePortForward,
flags.externalAddress,
// Mirror Node
flags.mirrorNodeId,
flags.mirrorNamespace,
],
};
static UPGRADE_FLAGS_LIST = {
required: [flags.deployment],
optional: [
flags.clusterRef,
flags.cacheDir,
flags.chartDirectory,
flags.explorerChartDirectory,
flags.enableIngress,
flags.ingressControllerValueFile,
flags.enableExplorerTls,
flags.explorerTlsHostName,
flags.explorerStaticIp,
flags.explorerVersion,
flags.namespace,
flags.quiet,
flags.soloChartVersion,
flags.tlsClusterIssuerType,
flags.valuesFile,
flags.clusterSetupNamespace,
flags.domainName,
flags.forcePortForward,
flags.externalAddress,
flags.id,
// Mirror Node
flags.mirrorNodeId,
flags.mirrorNamespace,
],
};
static DESTROY_FLAGS_LIST = {
required: [flags.deployment],
optional: [flags.chartDirectory, flags.clusterRef, flags.force, flags.quiet, flags.devMode],
};
async prepareHederaExplorerValuesArg(config) {
let valuesArgument = '';
if (config.valuesFile) {
valuesArgument += prepareValuesFiles(config.valuesFile);
}
if (config.enableIngress) {
valuesArgument += ' --set ingress.enabled=true';
valuesArgument += ` --set ingressClassName=${config.ingressReleaseName}`;
}
valuesArgument += ` --set fullnameOverride=${config.releaseName}-${config.namespace.name}`;
valuesArgument += ` --set proxyPass./api="http://${config.mirrorNodeReleaseName}-rest.${config.mirrorNamespace}.svc.cluster.local" `;
if (config.domainName) {
valuesArgument += helpers.populateHelmArguments({
'ingress.enabled': true,
'ingress.hosts[0].host': config.domainName,
});
if (config.tlsClusterIssuerType === 'self-signed') {
// Create TLS secret for Explorer
await KeyManager.createTlsSecret(this.k8Factory, config.namespace, config.domainName, config.cacheDir, constants.EXPLORER_INGRESS_TLS_SECRET_NAME);
if (config.enableIngress) {
valuesArgument += ` --set ingress.tls[0].hosts[0]=${config.domainName}`;
}
}
}
return valuesArgument;
}
async prepareCertManagerChartValuesArg(config) {
const { tlsClusterIssuerType, namespace } = config;
let valuesArgument = ' --install ';
if (!['acme-staging', 'acme-prod', 'self-signed'].includes(tlsClusterIssuerType)) {
throw new Error(`Invalid TLS cluster issuer type: ${tlsClusterIssuerType}, must be one of: "acme-staging", "acme-prod", or "self-signed"`);
}
if (!(await this.clusterChecks.isCertManagerInstalled())) {
valuesArgument += ' --set cert-manager.installCRDs=true';
}
if (tlsClusterIssuerType === 'self-signed') {
valuesArgument += ' --set selfSignedClusterIssuer.enabled=true';
}
else {
valuesArgument += ` --set global.explorerNamespace=${namespace}`;
valuesArgument += ' --set acmeClusterIssuer.enabled=true';
valuesArgument += ` --set certClusterIssuerType=${tlsClusterIssuerType}`;
}
if (config.valuesFile) {
valuesArgument += prepareValuesFiles(config.valuesFile);
}
return valuesArgument;
}
async prepareValuesArg(config) {
let valuesArgument = '';
if (config.valuesFile) {
valuesArgument += prepareValuesFiles(config.valuesFile);
}
return valuesArgument;
}
installCertManagerTask(commandType) {
return {
title: 'Install cert manager',
skip: ({ config }) => !config.enableExplorerTls,
task: async ({ config }) => {
config.soloChartVersion = SemanticVersion.getValidSemanticVersion(config.soloChartVersion, false, 'Solo chart version');
const { soloChartVersion } = config;
const soloCertManagerValuesArgument = await this.prepareCertManagerChartValuesArg(config);
// check if CRDs of cert-manager are already installed
let needInstall = false;
for (const crd of constants.CERT_MANAGER_CRDS) {
const crdExists = await this.k8Factory.getK8(config.clusterContext).crds().ifExists(crd);
if (!crdExists) {
needInstall = true;
break;
}
}
if (needInstall) {
// if cert-manager isn't already installed we want to install it separate from the certificate issuers
// as they will fail to be created due to the order of the installation being dependent on the cert-manager
// being installed first
await this.chartManager.upgrade(NamespaceName.of(constants.CERT_MANAGER_NAME_SPACE), constants.SOLO_CERT_MANAGER_CHART, constants.SOLO_CERT_MANAGER_CHART, config.chartDirectory || constants.SOLO_TESTING_CHART_URL, soloChartVersion, ' --install --create-namespace --set cert-manager.installCRDs=true', config.clusterContext, commandType !== ExplorerCommandType.ADD);
showVersionBanner(this.logger, constants.SOLO_CERT_MANAGER_CHART, soloChartVersion);
}
// wait cert-manager to be ready to proceed, otherwise may get error of "failed calling webhook"
await this.k8Factory
.getK8(config.clusterContext)
.pods()
.waitForReadyStatus(constants.DEFAULT_CERT_MANAGER_NAMESPACE, ['app.kubernetes.io/component=webhook', `app.kubernetes.io/instance=${constants.SOLO_CERT_MANAGER_CHART}`], constants.PODS_READY_MAX_ATTEMPTS, constants.PODS_READY_DELAY);
// sleep for a few seconds to allow cert-manager to be ready
if (commandType === ExplorerCommandType.UPGRADE) {
await sleep(Duration.ofSeconds(10));
}
await this.chartManager.upgrade(NamespaceName.of(constants.CERT_MANAGER_NAME_SPACE), constants.SOLO_CERT_MANAGER_CHART, constants.SOLO_CERT_MANAGER_CHART, config.chartDirectory || constants.SOLO_TESTING_CHART_URL, soloChartVersion, soloCertManagerValuesArgument, config.clusterContext);
showVersionBanner(this.logger, constants.SOLO_CERT_MANAGER_CHART, soloChartVersion, 'Upgraded');
},
};
}
installExplorerTask(commandType) {
return {
title: 'Install explorer',
task: async ({ config }) => {
config.explorerVersion = SemanticVersion.getValidSemanticVersion(config.explorerVersion, false, 'Explorer version');
let exploreValuesArgument = ' --install ';
exploreValuesArgument += prepareValuesFiles(constants.EXPLORER_VALUES_FILE);
exploreValuesArgument += await this.prepareHederaExplorerValuesArg(config);
// Local chart checkouts can keep appVersion/tag at placeholder values (for example 0.0.1),
// so pin the runtime image tag explicitly to the requested explorer version.
if (config.explorerChartDirectory) {
exploreValuesArgument += helpers.populateHelmArguments({ 'image.tag': config.explorerVersion });
}
await this.chartManager.upgrade(config.namespace, config.releaseName, '', config.explorerChartDirectory || constants.EXPLORER_CHART_URL, config.explorerVersion, exploreValuesArgument, config.clusterContext);
if (commandType === ExplorerCommandType.ADD) {
this.remoteConfig.configuration.components.changeComponentPhase(config.newExplorerComponent.metadata.id, ComponentTypes.Explorer, DeploymentPhase.DEPLOYED);
await this.remoteConfig.persist();
}
else if (commandType === ExplorerCommandType.UPGRADE) {
// update explorer version in remote config after successful upgrade
this.remoteConfig.updateComponentVersion(ComponentTypes.Explorer, new SemanticVersion(config.explorerVersion));
await this.remoteConfig.persist();
}
showVersionBanner(this.logger, config.releaseName, config.explorerVersion);
},
};
}
installExplorerIngressControllerTask() {
return {
title: 'Install explorer ingress controller',
skip: ({ config }) => !config.enableIngress,
task: async ({ config }) => {
let explorerIngressControllerValuesArgument = ' --install ';
if (config.explorerStaticIp !== '') {
explorerIngressControllerValuesArgument += ` --set controller.service.loadBalancerIP=${config.explorerStaticIp}`;
}
explorerIngressControllerValuesArgument += ` --set fullnameOverride=${config.ingressReleaseName}`;
explorerIngressControllerValuesArgument += ` --set controller.ingressClass=${config.ingressReleaseName}`;
explorerIngressControllerValuesArgument += ` --set controller.extraArgs.controller-class=${config.ingressReleaseName}`;
if (config.tlsClusterIssuerType === 'self-signed') {
explorerIngressControllerValuesArgument += prepareValuesFiles(config.ingressControllerValueFile);
}
await this.chartManager.upgrade(config.namespace, config.ingressReleaseName, constants.INGRESS_CONTROLLER_RELEASE_NAME, constants.INGRESS_CONTROLLER_RELEASE_NAME, INGRESS_CONTROLLER_VERSION, explorerIngressControllerValuesArgument, config.clusterContext);
showVersionBanner(this.logger, config.ingressReleaseName, INGRESS_CONTROLLER_VERSION);
const k8 = this.k8Factory.getK8(config.clusterContext);
// patch explorer ingress to use h1 protocol, haproxy ingress controller default backend protocol is h2
// to support grpc over http/2
await k8.ingresses().update(config.namespace, config.releaseName, {
metadata: {
annotations: {
'haproxy-ingress.github.io/backend-protocol': 'h1',
},
},
});
const ingressClasses = await k8.ingressClasses().list();
if (ingressClasses.some((ingressClass) => ingressClass.name === config.ingressReleaseName)) {
return;
}
await k8
.ingressClasses()
.create(config.ingressReleaseName, constants.INGRESS_CONTROLLER_PREFIX + config.ingressReleaseName);
},
};
}
checkExplorerPodIsReadyTask() {
return {
title: 'Check explorer pod is ready',
task: async ({ config }) => {
await this.k8Factory
.getK8(config.clusterContext)
.pods()
.waitForReadyStatus(config.namespace, Templates.renderExplorerLabels(config.id, config.isLegacyChartInstalled ? config.releaseName : undefined), constants.PODS_READY_MAX_ATTEMPTS, constants.PODS_READY_DELAY);
},
};
}
checkExplorerIngressControllerPodIsReadyTask() {
return {
title: 'Check haproxy ingress controller pod is ready',
skip: ({ config }) => !config.enableIngress,
task: async ({ config }) => {
await this.k8Factory
.getK8(config.clusterContext)
.pods()
.waitForReadyStatus(config.namespace, [
`app.kubernetes.io/name=${constants.INGRESS_CONTROLLER_RELEASE_NAME}`,
`app.kubernetes.io/instance=${config.ingressReleaseName}`,
], constants.PODS_READY_MAX_ATTEMPTS, constants.PODS_READY_DELAY);
},
};
}
enablePortForwardingTask() {
return {
title: 'Enable port forwarding for explorer',
skip: ({ config }) => !config.forcePortForward,
task: async ({ config }) => {
const externalAddress = this.configManager.getFlag(flags.externalAddress);
const pods = await this.k8Factory
.getK8(config.clusterContext)
.pods()
.list(config.namespace, Templates.renderExplorerLabels(config.id, config.isLegacyChartInstalled ? config.releaseName : undefined));
if (pods.length === 0) {
throw new SoloError('No Hiero Explorer pod found');
}
const podReference = pods[0].podReference;
await this.remoteConfig.configuration.components.stopPortForwards(config.clusterRef, podReference, constants.EXPLORER_PORT, // Pod port
constants.EXPLORER_LOCAL_PORT, // Local port
this.k8Factory.getK8(config.clusterContext), this.logger, ComponentTypes.Explorer, 'Explorer');
await this.remoteConfig.persist();
await this.remoteConfig.configuration.components.managePortForward(config.clusterRef, podReference, constants.EXPLORER_PORT, // Pod port
constants.EXPLORER_LOCAL_PORT, // Local port
this.k8Factory.getK8(config.clusterContext), this.logger, ComponentTypes.Explorer, 'Explorer', config.isChartInstalled, // Reuse existing port if chart is already installed
undefined, true, // persist: auto-restart on failure using persist-port-forward.js
externalAddress);
await this.remoteConfig.persist();
},
};
}
getReleaseName() {
return this.renderReleaseName(this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.Explorer));
}
getIngressReleaseName(namespaceName) {
return this.renderIngressReleaseName(this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.Explorer), namespaceName);
}
renderReleaseName(id) {
if (typeof id !== 'number') {
throw new SoloError(`Invalid component id: ${id}, type: ${typeof id}`);
}
return `${constants.EXPLORER_RELEASE_NAME}-${id}`;
}
renderIngressReleaseName(id, namespaceName) {
if (typeof id !== 'number') {
throw new SoloError(`Invalid component id: ${id}, type: ${typeof id}`);
}
const maxHelmReleaseNameLength = 53;
const baseReleaseName = `${constants.EXPLORER_INGRESS_CONTROLLER_RELEASE_NAME}-${id}-${namespaceName.name}`;
if (baseReleaseName.length <= maxHelmReleaseNameLength) {
return baseReleaseName;
}
// Keep names deterministic and short enough for Helm while preserving readability.
const hashSuffixLength = 8;
const namespaceHash = createHash('sha256')
.update(namespaceName.name)
.digest('hex')
.slice(0, hashSuffixLength);
const prefix = `${constants.EXPLORER_INGRESS_CONTROLLER_RELEASE_NAME}-${id}`;
const availableNamespaceLength = maxHelmReleaseNameLength - prefix.length - 1 - hashSuffixLength - 1; /* - */
if (availableNamespaceLength <= 0) {
return `${prefix}-${namespaceHash}`;
}
const shortenedNamespace = namespaceName.name.slice(0, availableNamespaceLength);
return `${prefix}-${shortenedNamespace}-${namespaceHash}`;
}
async add(argv) {
let lease;
const tasks = this.taskList.newTaskList([
{
title: 'Initialize',
task: async (context_, task) => {
await this.localConfig.load();
await this.remoteConfig.loadAndValidate(argv);
if (!this.oneShotState.isActive()) {
lease = await this.leaseManager.create();
}
this.configManager.update(argv);
flags.disablePrompts(ExplorerCommand_1.DEPLOY_FLAGS_LIST.optional);
const allFlags = [
...ExplorerCommand_1.DEPLOY_FLAGS_LIST.optional,
...ExplorerCommand_1.DEPLOY_FLAGS_LIST.required,
];
await this.configManager.executePrompt(task, allFlags);
const config = this.configManager.getConfig(ExplorerCommand_1.DEPLOY_CONFIGS_NAME, allFlags, []);
// In concurrent one-shot execution, configManager may have stale data due to
// interleaved updates from other sub-commands. Override with argv values directly.
if (this.oneShotState.isActive() && argv[flags.explorerVersion.name]) {
config.explorerVersion = argv[flags.explorerVersion.name];
}
config.isLegacyChartInstalled = false;
context_.config = config;
config.clusterRef = this.getClusterReference();
config.clusterContext = this.getClusterContext(config.clusterRef);
config.releaseName = this.getReleaseName();
config.ingressReleaseName = this.getIngressReleaseName(config.namespace);
const { mirrorNodeId, mirrorNamespace, mirrorNodeReleaseName } = await this.inferMirrorNodeData(config.namespace, config.clusterContext);
config.mirrorNodeId = mirrorNodeId;
config.mirrorNamespace = mirrorNamespace;
config.mirrorNodeReleaseName = mirrorNodeReleaseName;
config.newExplorerComponent = this.componentFactory.createNewExplorerComponent(config.clusterRef, config.namespace);
config.newExplorerComponent.metadata.phase = DeploymentPhase.REQUESTED;
config.id = config.newExplorerComponent.metadata.id;
config.valuesArg = await this.prepareValuesArg(context_.config);
await this.throwIfNamespaceIsMissing(config.clusterContext, config.namespace);
if (!this.oneShotState.isActive()) {
return ListrLock.newAcquireLockTask(lease, task);
}
return ListrLock.newSkippedLockTask(task);
},
},
this.loadRemoteConfigTask(argv),
this.addExplorerComponents(),
this.installCertManagerTask(ExplorerCommandType.ADD),
this.installExplorerTask(ExplorerCommandType.ADD),
this.installExplorerIngressControllerTask(),
this.checkExplorerPodIsReadyTask(),
this.checkExplorerIngressControllerPodIsReadyTask(),
this.enablePortForwardingTask(),
{
title: 'Show user messages',
skip: () => !this.oneShotState.isActive(),
task: () => {
this.logger.showAllMessageGroups();
},
},
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'explorer node add');
if (tasks.isRoot()) {
try {
await tasks.run();
this.logger.debug('explorer deployment has completed');
}
catch (error) {
throw new SoloError(`Error deploying explorer: ${error.message}`, error);
}
finally {
if (!this.oneShotState.isActive()) {
await lease?.release();
}
}
}
else {
this.taskList.registerCloseFunction(async () => {
if (!this.oneShotState.isActive()) {
await lease?.release();
}
});
}
return true;
}
async upgrade(argv) {
let lease;
const tasks = this.taskList.newTaskList([
{
title: 'Initialize',
task: async (context_, task) => {
await this.localConfig.load();
await this.remoteConfig.loadAndValidate(argv);
if (!this.oneShotState.isActive()) {
lease = await this.leaseManager.create();
}
this.configManager.update(argv);
flags.disablePrompts(ExplorerCommand_1.UPGRADE_FLAGS_LIST.optional);
const allFlags = [
...ExplorerCommand_1.UPGRADE_FLAGS_LIST.optional,
...ExplorerCommand_1.UPGRADE_FLAGS_LIST.required,
];
await this.configManager.executePrompt(task, allFlags);
const config = this.configManager.getConfig(ExplorerCommand_1.UPGRADE_CONFIGS_NAME, allFlags, []);
context_.config = config;
config.clusterRef = this.getClusterReference();
config.clusterContext = this.getClusterContext(config.clusterRef);
const { id, releaseName, ingressReleaseName, isChartInstalled, isLegacyChartInstalled } = await this.inferExplorerData(config.namespace, config.clusterContext);
config.id = id;
config.releaseName = releaseName;
config.ingressReleaseName = ingressReleaseName;
config.isChartInstalled = isChartInstalled;
config.isLegacyChartInstalled = isLegacyChartInstalled;
const { mirrorNodeId, mirrorNamespace, mirrorNodeReleaseName } = await this.inferMirrorNodeData(config.namespace, config.clusterContext);
config.mirrorNodeId = mirrorNodeId;
config.mirrorNamespace = mirrorNamespace;
config.mirrorNodeReleaseName = mirrorNodeReleaseName;
config.valuesArg = await this.prepareValuesArg(context_.config);
assertUpgradeVersionNotOlder('Explorer', config.explorerVersion, this.remoteConfig.getComponentVersion(ComponentTypes.Explorer), optionFromFlag(flags.explorerVersion));
await this.throwIfNamespaceIsMissing(config.clusterContext, config.namespace);
if (!this.oneShotState.isActive()) {
return ListrLock.newAcquireLockTask(lease, task);
}
return ListrLock.newSkippedLockTask(task);
},
},
this.loadRemoteConfigTask(argv),
this.installCertManagerTask(ExplorerCommandType.UPGRADE),
this.installExplorerTask(ExplorerCommandType.UPGRADE),
this.installExplorerIngressControllerTask(),
this.checkExplorerPodIsReadyTask(),
this.checkExplorerIngressControllerPodIsReadyTask(),
this.enablePortForwardingTask(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'explorer node upgrade');
if (tasks.isRoot()) {
try {
await tasks.run();
this.logger.debug('explorer upgrading has completed');
}
catch (error) {
throw new SoloError(`Error upgrading explorer: ${error.message}`, error);
}
finally {
if (!this.oneShotState.isActive()) {
await lease?.release();
}
}
}
else {
this.taskList.registerCloseFunction(async () => {
if (!this.oneShotState.isActive()) {
await lease?.release();
}
});
}
return true;
}
async destroy(argv) {
let lease;
const tasks = this.taskList.newTaskList([
{
title: 'Initialize',
task: async (context_, task) => {
await this.localConfig.load();
await this.loadRemoteConfigOrWarn(argv);
if (!this.oneShotState.isActive()) {
lease = await this.leaseManager.create();
}
if (!argv.force) {
const confirmResult = await task.prompt(ListrInquirerPromptAdapter).run(confirmPrompt, {
default: false,
message: 'Are you sure you would like to destroy the explorer?',
});
if (!confirmResult) {
throw new UserBreak('Aborted application by user prompt');
}
}
this.configManager.update(argv);
const namespace = await this.getNamespace(task);
const clusterReference = this.getClusterReference();
const clusterContext = this.getClusterContext(clusterReference);
const { id, releaseName, ingressReleaseName, isChartInstalled, isLegacyChartInstalled } = await this.inferExplorerData(namespace, clusterContext);
context_.config = {
namespace,
clusterContext,
clusterReference,
id,
releaseName,
ingressReleaseName,
isChartInstalled,
isLegacyChartInstalled,
};
await this.throwIfNamespaceIsMissing(clusterContext, namespace);
if (!this.oneShotState.isActive()) {
return ListrLock.newAcquireLockTask(lease, task);
}
return ListrLock.newSkippedLockTask(task);
},
},
this.loadRemoteConfigTask(argv, true),
this.loadRemoteConfigTask(argv),
{
title: 'Destroy explorer',
task: async (context_) => {
await this.chartManager.uninstall(context_.config.namespace, context_.config.releaseName, context_.config.clusterContext);
},
skip: (context_) => !context_.config.isChartInstalled,
},
{
title: 'Uninstall explorer ingress controller',
task: async (context_) => {
await this.chartManager.uninstall(context_.config.namespace, context_.config.ingressReleaseName);
// destroy ingress class if found one
const existingIngressClasses = await this.k8Factory
.getK8(context_.config.clusterContext)
.ingressClasses()
.list();
existingIngressClasses.map((ingressClass) => {
if (ingressClass.name === context_.config.ingressReleaseName) {
this.k8Factory
.getK8(context_.config.clusterContext)
.ingressClasses()
.delete(context_.config.ingressReleaseName);
}
});
},
},
this.disableMirrorNodeExplorerComponents(),
], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'explorer node destroy');
if (tasks.isRoot()) {
try {
await tasks.run();
}
catch (error) {
throw new SoloError(`Error destroy explorer: ${error.message}`, error);
}
finally {
if (!this.oneShotState.isActive()) {
await lease?.release();
}
}
}
else {
this.taskList.registerCloseFunction(async () => {
if (!this.oneShotState.isActive()) {
await lease?.release();
}
});
}
return true;
}
loadRemoteConfigTask(argv, safe = false) {
return {
title: 'Load remote config',
task: async () => {
if (safe) {
await this.loadRemoteConfigOrWarn(argv);
return;
}
await this.remoteConfig.loadAndValidate(argv);
},
};
}
/** Removes the explorer components from remote config. */
disableMirrorNodeExplorerComponents() {
return {
title: 'Remove explorer from remote config',
skip: () => !this.remoteConfig.isLoaded(),
task: async ({ config }) => {
this.remoteConfig.configuration.components.removeComponent(config.id, ComponentTypes.Explorer);
await this.remoteConfig.persist();
},
};
}
/** Adds the explorer components to remote config. */
addExplorerComponents() {
return {
title: 'Add explorer to remote config',
skip: () => !this.remoteConfig.isLoaded() || this.oneShotState.isActive(),
task: async ({ config }) => {
this.remoteConfig.configuration.components.addNewComponent(config.newExplorerComponent, ComponentTypes.Explorer);
// update explorer version in remote config
this.remoteConfig.updateComponentVersion(ComponentTypes.Explorer, new SemanticVersion(config.explorerVersion));
await this.remoteConfig.persist();
},
};
}
async close() { } // no-op
async checkIfLegacyChartIsInstalled(id, namespace, context) {
return id <= 1
? await this.chartManager.isChartInstalled(namespace, constants.EXPLORER_RELEASE_NAME, context)
: false;
}
inferExplorerId() {
const id = this.configManager.getFlag(flags.id);
if (typeof id === 'number') {
return id;
}
if (!this.remoteConfig.configuration.components.state.explorers[0]) {
throw new SoloError('No explorer component found in remote config');
}
return this.remoteConfig.configuration.components.state.explorers[0].metadata.id;
}
async inferExplorerData(namespace, context) {
const id = this.inferExplorerId();
const isLegacyChartInstalled = await this.checkIfLegacyChartIsInstalled(id, namespace, context);
if (isLegacyChartInstalled) {
return {
id,
releaseName: constants.EXPLORER_RELEASE_NAME,
isChartInstalled: true,
ingressReleaseName: constants.EXPLORER_INGRESS_CONTROLLER_RELEASE_NAME,
isLegacyChartInstalled,
};
}
const releaseName = this.renderReleaseName(id);
return {
id,
releaseName,
ingressReleaseName: this.renderIngressReleaseName(id, namespace),
isChartInstalled: await this.chartManager.isChartInstalled(namespace, releaseName, context),
isLegacyChartInstalled,
};
}
};
ExplorerCommand = ExplorerCommand_1 = __decorate([
injectable(),
__param(0, inject(InjectTokens.ClusterChecks)),
__metadata("design:paramtypes", [Function])
], ExplorerCommand);
export { ExplorerCommand };
//# sourceMappingURL=explorer.js.map