UNPKG

@hashgraph/solo

Version:

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

334 lines 17 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); } }; var CacheCommand_1; import { SoloError } from '../core/errors/solo-error.js'; import * as constants from '../core/constants.js'; import { BaseCommand } from './base.js'; import { Flags as flags } from './flags.js'; import { inject, injectable } from 'tsyringe-neo'; import { ImageCacheHandlerBuilder } from '../integration/cache/impl/image-cache-handler-builder.js'; import { InjectTokens } from '../core/dependency-injection/inject-tokens.js'; import { patchInject } from '../core/dependency-injection/container-helper.js'; import fs from 'node:fs/promises'; import { CacheImageTargetTemplateRenderer } from '../integration/cache/impl/cache-image-target-template-renderer.js'; import { PathEx } from '../business/utils/path-ex.js'; import { CacheImageTemplateValues } from '../integration/cache/models/impl/cache-image-template-values.js'; import * as version from '../../version.js'; import { DefaultCacheImageTemplateResolver } from '../integration/cache/impl/default-cache-image-template-resolver.js'; let CacheCommand = class CacheCommand extends BaseCommand { static { CacheCommand_1 = this; } containerEngineClient; static CACHE_NOT_MATERIALIZED_ERROR_MESSAGE = 'Cache image targets have not been materialized yet. Run `solo cache image pull` first.'; constructor(containerEngineClient) { super(); this.containerEngineClient = containerEngineClient; this.containerEngineClient = patchInject(containerEngineClient, InjectTokens.ContainerEngineClient, this.constructor.name); } async close() { } // ------ Flags ------ // static PULL_FLAGS_LIST = { required: [], optional: [flags.quiet, flags.cacheDir, flags.devMode, flags.edgeEnabled], }; static LOAD_FLAGS_LIST = { required: [], optional: [flags.quiet, flags.cacheDir, flags.devMode, flags.clusterRef], }; static LIST_FLAGS_LIST = { required: [], optional: [flags.quiet, flags.cacheDir, flags.devMode], }; static CLEAR_FLAGS_LIST = { required: [], optional: [flags.quiet, flags.cacheDir, flags.devMode], }; static STATUS_FLAGS_LIST = { required: [], optional: [flags.quiet, flags.cacheDir, flags.devMode, flags.clusterRef], }; // ----- Handlers ------- // async pull(argv) { const tasks = this.taskList.newTaskList([ { title: 'Initialize', task: async (context_, task) => { this.configManager.update(argv); flags.disablePrompts(CacheCommand_1.PULL_FLAGS_LIST.optional); const allFlags = [ ...CacheCommand_1.PULL_FLAGS_LIST.required, ...CacheCommand_1.PULL_FLAGS_LIST.optional, ]; await this.configManager.executePrompt(task, allFlags); const edgeEnabled = this.configManager.getFlag(flags.edgeEnabled); const renderedYamlPath = await this.renderImageTargetsFile(edgeEnabled); context_.config = { imageCacheHandler: await this.buildImageCacheHandlerFromYaml(renderedYamlPath), results: [], edgeEnabled, }; }, }, this.pullAndCacheContainerImages(), this.showUserMessages(), ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'cache image pull'); if (tasks.isRoot()) { try { await tasks.run(); } catch (error) { throw new SoloError(`Error pulling cache: ${error.message}`, error); } } else { this.taskList.registerCloseFunction(async () => { }); } return true; } async load(argv) { const tasks = this.taskList.newTaskList([ { title: 'Initialize', task: async (context_, task) => { await this.localConfig.load(); this.configManager.update(argv); flags.disablePrompts(CacheCommand_1.LOAD_FLAGS_LIST.optional); const allFlags = [ ...CacheCommand_1.LOAD_FLAGS_LIST.required, ...CacheCommand_1.LOAD_FLAGS_LIST.optional, ]; await this.configManager.executePrompt(task, allFlags); const clusterReference = this.getClusterReference(); const context = this.getClusterContext(clusterReference); const cacheDirectory = this.configManager.getFlag(flags.cacheDir); context_.config = { imageCacheHandler: await this.buildImageCacheHandlerFromRenderedFile(cacheDirectory), clusterReference, context, }; }, }, this.loadImagesIntoCluster(), this.showUserMessages(), ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'cache image load'); if (tasks.isRoot()) { try { await tasks.run(); } catch (error) { throw new SoloError(`Error loading from cache: ${error.message}`, error); } } else { this.taskList.registerCloseFunction(async () => { }); } return true; } async list() { const tasks = this.taskList.newTaskList([ { title: 'List cached images', task: async (context_) => { const cacheDirectory = this.configManager.getFlag(flags.cacheDir); const config = { imageCacheHandler: await this.buildImageCacheHandlerFromRenderedFile(cacheDirectory), }; context_.config = config; const cachedItems = await config.imageCacheHandler.list(); try { this.logger.showList(`Cached images: [${cachedItems.length}]`, cachedItems.map((item) => `${item.target.name}:${item.target.version}`)); } catch { this.logger.warn('No cache manifest found'); } }, }, ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'cache image list'); await tasks.run(); return true; } async clear() { const tasks = this.taskList.newTaskList([ { title: 'Clear image cache', task: async (context_) => { const cacheDirectory = this.configManager.getFlag(flags.cacheDir); const renderedYamlPath = this.getRenderedImageTargetsFilePath(cacheDirectory); try { const config = { imageCacheHandler: await this.buildImageCacheHandlerFromRenderedFile(cacheDirectory), }; context_.config = config; await config.imageCacheHandler.clear(); } catch (error) { if (!(error instanceof SoloError) || error.message !== CacheCommand_1.CACHE_NOT_MATERIALIZED_ERROR_MESSAGE) { throw error; } } await fs.rm(renderedYamlPath, { force: true }); }, }, ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'cache image clear'); await tasks.run(); return true; } async status(argv) { const tasks = this.taskList.newTaskList([ { title: 'Check cache status', task: async (context_, task) => { this.configManager.update(argv); flags.disablePrompts(CacheCommand_1.STATUS_FLAGS_LIST.optional); const allFlags = [ ...CacheCommand_1.STATUS_FLAGS_LIST.required, ...CacheCommand_1.STATUS_FLAGS_LIST.optional, ]; await this.configManager.executePrompt(task, allFlags); const cacheDirectory = this.configManager.getFlag(flags.cacheDir); const config = { imageCacheHandler: await this.buildImageCacheHandlerFromRenderedFile(cacheDirectory), }; const clusterReference = this.configManager.getFlag(flags.clusterRef); if (clusterReference) { await this.localConfig.load(); const context = this.getClusterContext(clusterReference); config.clusterReference = clusterReference; config.context = context; config.clusterName = this.prepareClusterName(this.k8Factory.getK8(context).clusters().readCurrent()); } else { try { config.clusterName = this.prepareClusterName(this.k8Factory.default().clusters().readCurrent()); } catch { // Best effort only. Local cache status should still work. } } context_.config = config; const items = await config.imageCacheHandler.healthcheck(); const cachedItems = await config.imageCacheHandler.list(); const expectedTargets = await config.imageCacheHandler.resolveRequiredArtifacts(); const expectedImages = expectedTargets.map((target) => `${target.name}:${target.version}`); const missingImages = items .filter((item) => !item.healthy) .map((item) => `${item.target.name}:${item.target.version}`); let totalBytes = 0; for (const item of cachedItems) { try { const stat = await fs.stat(item.localPath); totalBytes += stat.size; } catch { // missing files are already reflected by healthcheck } } const totalSizeMb = (totalBytes / (1024 * 1024)).toFixed(2); this.logger.showUser(`Cached images: ${items.length}`); this.logger.showUser(`Total size: ${totalSizeMb} MB`); this.logger.showUser(`Healthy: ${missingImages.length === 0}`); if (missingImages.length > 0) { this.logger.showList('Missing cache archives', missingImages); } if (!config.clusterName) { this.logger.showUser('Cluster images: unavailable'); return; } try { const clusterImages = await this.containerEngineClient.listLoadedImagesInCluster(config.clusterName); const clusterImageSet = new Set(clusterImages); const expectedImageSet = new Set(expectedImages); const loadedExpectedImages = expectedImages.filter((image) => clusterImageSet.has(image)); const missingInCluster = expectedImages.filter((image) => !clusterImageSet.has(image)); const additionalClusterImages = clusterImages.filter((image) => !expectedImageSet.has(image)); this.logger.showUser(`Cluster loaded expected images: ${loadedExpectedImages.length}/${expectedImages.length}`); if (missingInCluster.length > 0) { this.logger.showList('Expected but not loaded in cluster', missingInCluster); } if (additionalClusterImages.length > 0) { this.logger.showList('Additional images loaded in cluster', additionalClusterImages); } } catch (error) { const message = error instanceof Error ? error.message : String(error); this.logger.showUser(`Cluster images: failed to inspect (${message})`); } }, }, ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'cache image status'); await tasks.run(); return true; } // ------ Tasks ------ // pullAndCacheContainerImages() { return { title: 'Pull and cache container images', task: async ({ config: { imageCacheHandler } }, task) => { return task.newListr(await imageCacheHandler.pull(), constants.LISTR_DEFAULT_OPTIONS.WITH_CONCURRENCY); }, }; } loadImagesIntoCluster() { return { title: 'Load images into cluster', task: async ({ config: { imageCacheHandler, context } }, task) => { const subTasks = []; const newTasks = await imageCacheHandler.load(this.prepareClusterName(this.k8Factory.getK8(context).clusters().readCurrent())); subTasks.push(...newTasks); return task.newListr(subTasks, constants.LISTR_DEFAULT_OPTIONS.WITH_CONCURRENCY); }, }; } showUserMessages() { return { title: 'Show user messages', skip: () => this.oneShotState.isActive(), task: () => { this.logger.showAllMessageGroups(); }, }; } // ------ Helpers ------ // prepareClusterName(clusterReference) { return clusterReference.startsWith('kind-') ? clusterReference.replace('kind-', '') : clusterReference; } getRenderedImageTargetsFilePath(cacheDirectory) { return PathEx.join(cacheDirectory, 'config', CacheImageTargetTemplateRenderer.RENDERED_FILE_NAME); } async renderImageTargetsFile(edgeEnabled) { const cacheDirectory = this.configManager.getFlag(flags.cacheDir); const renderedConfigDirectory = PathEx.join(cacheDirectory, 'config'); return new CacheImageTargetTemplateRenderer(new DefaultCacheImageTemplateResolver(new CacheImageTemplateValues(edgeEnabled ? version.MIRROR_NODE_EDGE_VERSION : version.MIRROR_NODE_VERSION, edgeEnabled ? version.BLOCK_NODE_EDGE_VERSION : version.BLOCK_NODE_VERSION, edgeEnabled ? version.HEDERA_JSON_RPC_RELAY_EDGE_VERSION : version.HEDERA_JSON_RPC_RELAY_VERSION, edgeEnabled ? version.EXPLORER_EDGE_VERSION : version.EXPLORER_VERSION, version.MINIO_OPERATOR_VERSION, edgeEnabled ? version.HEDERA_PLATFORM_EDGE_VERSION : version.HEDERA_PLATFORM_VERSION))).renderToFile(constants.SOLO_CACHE_IMAGES_TARGET_FILE, renderedConfigDirectory); } async buildImageCacheHandlerFromYaml(filePath) { return ImageCacheHandlerBuilder.fromYaml(filePath).engine(this.containerEngineClient).build(); } async buildImageCacheHandlerFromRenderedFile(cacheDirectory) { const renderedYamlPath = this.getRenderedImageTargetsFilePath(cacheDirectory); try { await fs.access(renderedYamlPath); } catch { throw new SoloError(CacheCommand_1.CACHE_NOT_MATERIALIZED_ERROR_MESSAGE); } return this.buildImageCacheHandlerFromYaml(renderedYamlPath); } }; CacheCommand = CacheCommand_1 = __decorate([ injectable(), __param(0, inject(InjectTokens.ContainerEngineClient)), __metadata("design:paramtypes", [Object]) ], CacheCommand); export { CacheCommand }; //# sourceMappingURL=cache.js.map