UNPKG

@hashgraph/solo

Version:

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

241 lines 9.35 kB
// SPDX-License-Identifier: Apache-2.0 import { spawn } from 'node:child_process'; import { HelmExecutionException } from '../helm-execution-exception.js'; import { HelmParserException } from '../helm-parser-exception.js'; import { SensitiveDataRedactor } from '../../../core/util/sensitive-data-redactor.js'; /** * Represents the execution of a helm command and is responsible for parsing the response. */ export class HelmExecution { /** * The logger for this class which should be used for all logging. */ static MSG_TIMEOUT_ERROR = 'Timed out waiting for the process to complete'; /** * The message for a timeout error. */ static MSG_DESERIALIZATION_ERROR = 'Failed to deserialize the output into the specified class: %s'; /** * The message for a deserialization error. */ static MSG_LIST_DESERIALIZATION_ERROR = 'Failed to deserialize the output into a list of the specified class: %s'; process; commandLine; logger; output = []; errOutput = []; exitCodeValue = null; /** * Redacts sensitive arguments from a command array. * Delegates to the shared {@link SensitiveDataRedactor} utility. * @param command The command array to redact * @returns A new redacted command array */ static redactCommand(command) { return SensitiveDataRedactor.redactArguments(command, { flagsToRedactNextArgument: ['--password'], setStyleFlags: ['--set', '--set-string', '--set-file'], }); } /** * Creates a new HelmExecution instance. * @param command The command array to execute * @param environmentVariables The environment variables to set * @param logger Optional logger for command output */ constructor(command, environmentVariables, logger) { this.logger = logger; const redactedCommand = HelmExecution.redactCommand(command); this.commandLine = redactedCommand.join(' '); if (this.logger) { this.logger.info(`Executing helm command: ${this.commandLine}`); } this.process = spawn(command.join(' '), { shell: true, env: { ...process.env, ...environmentVariables }, }); } /** * Waits for the process to complete. * @returns A promise that resolves when the process completes */ async waitFor() { return new Promise((resolve, reject) => { // const output: string[] = []; this.process.stdout.on('data', (d) => { const items = d.toString().split(/\r?\n/); for (const item of items) { if (item) { this.output.push(item); } } }); this.process.stderr.on('data', (d) => { const items = d.toString().split(/\r?\n/); for (const item of items) { if (item) { this.errOutput.push(item.trim()); } } }); this.process.on('close', (code) => { this.exitCodeValue = code; if (code === 0) { resolve(); } else { reject(new HelmExecutionException(code || 1, `Helm command failed with exit code ${code}. Command: '${this.commandLine}'. Error: ${this.standardError()}`, this.standardOutput(), this.standardError())); } }); }); } /** * Waits for the process to complete with a timeout. * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves with true if the process completed, or false if it timed out */ async waitForTimeout(timeout) { const timeoutPromise = new Promise((resolve) => { setTimeout(() => resolve(false), timeout.toMillis()); }); const successPromise = new Promise((resolve) => { this.process.on('close', (code) => { resolve(code === 0); }); }); return Promise.race([successPromise, timeoutPromise]); } /** * Gets the exit code of the process. * @returns The exit code or null if the process hasn't completed */ exitCode() { return this.exitCodeValue; } /** * Gets the standard output of the process. * @returns concatenated standard output as a string */ standardOutput() { return this.output.join(''); } /** * Gets the standard error of the process. * @returns concatenated standard error as a string */ standardError() { return this.errOutput.join(''); } /** * Gets the response as a parsed object. * @param responseClass The class to parse the response into * @returns A promise that resolves with the parsed response */ async responseAs(responseClass) { return this.responseAsTimeout(responseClass, null); } /** * Gets the response as a parsed object with a timeout. * @param responseClass The class to parse the response into * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves with the parsed response or rejects on timeout */ async responseAsTimeout(responseClass, timeout) { if (timeout === null) { await this.waitFor(); } else { const success = await this.waitForTimeout(timeout); if (!success) { throw new HelmParserException(HelmExecution.MSG_TIMEOUT_ERROR); } } const exitCode = this.exitCode(); if (exitCode !== 0) { const stdOut = this.standardOutput(); const stdError = this.standardError(); throw new HelmExecutionException(exitCode, `Helm command failed with exit code ${exitCode}. Command: '${this.commandLine}'. Error: ${stdError}`, stdOut, stdError); } if (responseClass === undefined) { return null; } const output = this.standardOutput(); try { const parsed = JSON.parse(output); const result = new responseClass(); Object.assign(result, parsed); return result; } catch { throw new HelmParserException(HelmExecution.MSG_DESERIALIZATION_ERROR.replace('%s', responseClass.name)); } } /** * Gets the response as a list of parsed objects. * @param responseClass The class to parse each item in the response into * @returns A promise that resolves with the parsed response list */ async responseAsList(responseClass) { return this.responseAsListTimeout(responseClass, null); } /** * Gets the response as a list of parsed objects with a timeout. * @param responseClass The class to parse each item in the response into * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves with the parsed response list or rejects on timeout */ async responseAsListTimeout(responseClass, timeout) { if (timeout === null) { await this.waitFor(); } else { const success = await this.waitForTimeout(timeout); if (!success) { throw new HelmParserException(HelmExecution.MSG_TIMEOUT_ERROR); } } const exitCode = this.exitCode(); if (exitCode !== 0) { const stdOut = this.standardOutput(); const stdError = this.standardError(); throw new HelmExecutionException(exitCode, `Helm command failed with exit code ${exitCode}. Command: '${this.commandLine}'. Error: ${stdError}`, stdOut, stdError); } const output = this.standardOutput(); try { return JSON.parse(output); } catch { throw new HelmParserException(HelmExecution.MSG_LIST_DESERIALIZATION_ERROR.replace('%s', responseClass.name)); } } /** * Executes the command and waits for completion. * @returns A promise that resolves when the command completes */ async call() { await this.callTimeout(null); } /** * Executes the command and waits for completion with a timeout. * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves when the command completes or rejects on timeout */ async callTimeout(timeout) { if (timeout === null) { await this.waitFor(); } else { const success = await this.waitForTimeout(timeout); if (!success) { throw new HelmParserException(HelmExecution.MSG_TIMEOUT_ERROR); } } const exitCode = this.exitCode(); if (exitCode !== 0) { const stdOut = this.standardOutput(); const stdError = this.standardError(); throw new HelmExecutionException(exitCode, `Helm command failed with exit code ${exitCode}. Command: '${this.commandLine}'. Error: ${stdError}`, stdOut, stdError); } } } //# sourceMappingURL=helm-execution.js.map