/** * Helm Executor * Wraps Helm CLI operations */ import { exec } from 'child_process' import { promisify } from 'util' import { logger } from './logger.js' const execAsync = promisify(exec) export interface HelmInstallOptions { releaseName: string chart: string namespace?: string valuesFile?: string values?: Record version?: string wait?: boolean timeout?: string } export interface HelmUpgradeOptions extends HelmInstallOptions { reuseValues?: boolean } export class HelmExecutor { /** * Check if Helm is available */ async checkHelmInstalled(): Promise { try { await execAsync('helm version') return true } catch { return false } } /** * Install Helm chart */ async install(options: HelmInstallOptions): Promise<{ stdout: string; stderr: string }> { const args: string[] = [ 'install', options.releaseName, options.chart, ] if (options.namespace) { args.push('--namespace', options.namespace) } if (options.valuesFile) { args.push('--values', options.valuesFile) } if (options.version) { args.push('--version', options.version) } if (options.wait) { args.push('--wait') } if (options.timeout) { args.push('--timeout', options.timeout) } if (options.values) { // Write values to temp file and use --set-file or --set // For now, we'll use --set for simple values for (const [key, value] of Object.entries(options.values)) { args.push('--set', `${key}=${JSON.stringify(value)}`) } } logger.info('Executing Helm install', { args }) return execAsync(`helm ${args.join(' ')}`) } /** * Upgrade Helm release */ async upgrade(options: HelmUpgradeOptions): Promise<{ stdout: string; stderr: string }> { const args: string[] = [ 'upgrade', options.releaseName, options.chart, ] if (options.namespace) { args.push('--namespace', options.namespace) } if (options.valuesFile) { args.push('--values', options.valuesFile) } if (options.version) { args.push('--version', options.version) } if (options.wait) { args.push('--wait') } if (options.timeout) { args.push('--timeout', options.timeout) } if (options.reuseValues) { args.push('--reuse-values') } logger.info('Executing Helm upgrade', { args }) return execAsync(`helm ${args.join(' ')}`) } /** * Uninstall Helm release */ async uninstall(releaseName: string, namespace?: string): Promise<{ stdout: string; stderr: string }> { const args: string[] = ['uninstall', releaseName] if (namespace) { args.push('--namespace', namespace) } logger.info('Executing Helm uninstall', { args }) return execAsync(`helm ${args.join(' ')}`) } /** * Get release status */ async status(releaseName: string, namespace?: string): Promise { const args: string[] = ['status', releaseName, '--output', 'json'] if (namespace) { args.push('--namespace', namespace) } try { const { stdout } = await execAsync(`helm ${args.join(' ')}`) return JSON.parse(stdout) } catch (error) { logger.error('Failed to get Helm status', { error }) throw error } } /** * List releases */ async list(namespace?: string): Promise { const args: string[] = ['list', '--output', 'json'] if (namespace) { args.push('--namespace', namespace) } try { const { stdout } = await execAsync(`helm ${args.join(' ')}`) return JSON.parse(stdout) } catch (error) { logger.error('Failed to list Helm releases', { error }) throw error } } } export const helmExecutor = new HelmExecutor()