Skip to main content
Glama
helm-utils.ts5.83 kB
/** * Helm Utilities - Shared functions for Helm chart operations */ import * as path from 'path'; import * as fs from 'fs'; import { HelmChartInfo } from './helm-types'; import { execAsync } from './platform-utils'; /** * Sanitize input for safe shell command usage. * Validates that input contains only safe characters to prevent command injection. * @throws Error if input contains potentially dangerous characters */ export function sanitizeShellArg(arg: string, fieldName: string = 'argument'): string { // Allow alphanumeric, dash, underscore, dot, forward slash, colon (for URLs), and @ (for versions) // This covers valid Helm chart names, repo names, URLs, and version strings if (!/^[a-zA-Z0-9\-_./:\\@]+$/.test(arg)) { throw new Error(`Invalid characters in ${fieldName}: "${arg}". Only alphanumeric characters, dashes, underscores, dots, forward slashes, colons, and @ are allowed.`); } return arg; } /** * Validate and sanitize HelmChartInfo for safe shell command usage */ export function sanitizeChartInfo(chart: HelmChartInfo): { repositoryName: string; repository: string; chartName: string; version?: string; } { return { repositoryName: sanitizeShellArg(chart.repositoryName, 'repository name'), repository: sanitizeShellArg(chart.repository, 'repository URL'), chartName: sanitizeShellArg(chart.chartName, 'chart name'), version: chart.version ? sanitizeShellArg(chart.version, 'version') : undefined }; } /** * Build the Helm command from chart info and deployment options */ export function buildHelmCommand( chart: HelmChartInfo, releaseName: string, namespace: string, valuesPath?: string ): string { // Sanitize all inputs to prevent command injection const safeChart = sanitizeChartInfo(chart); const safeReleaseName = sanitizeShellArg(releaseName, 'release name'); const safeNamespace = sanitizeShellArg(namespace, 'namespace'); const parts = [ 'helm upgrade --install', safeReleaseName, `${safeChart.repositoryName}/${safeChart.chartName}`, `--namespace ${safeNamespace}`, '--create-namespace' ]; if (safeChart.version) { parts.push(`--version ${safeChart.version}`); } if (valuesPath) { // Values path is internally generated, but sanitize anyway parts.push(`-f ${sanitizeShellArg(valuesPath, 'values path')}`); } return parts.join(' '); } /** * Ensure Helm repository is added and updated */ export async function ensureHelmRepo(chart: HelmChartInfo): Promise<void> { // Sanitize chart info to prevent command injection const safeChart = sanitizeChartInfo(chart); await execAsync(`helm repo add ${safeChart.repositoryName} ${safeChart.repository} 2>/dev/null || true`); await execAsync('helm repo update 2>/dev/null || true'); } /** * Execute a Helm command with proper error handling */ export async function executeHelmCommand( command: string, options?: { timeout?: number; maxBuffer?: number; } ): Promise<{ stdout: string; stderr: string }> { const execOptions = { maxBuffer: options?.maxBuffer || 10 * 1024 * 1024, timeout: options?.timeout }; return await execAsync(command, execOptions); } /** * Get the path for Helm values file */ export function getHelmValuesPath(solutionId: string): string { const tmpDir = path.join(process.cwd(), 'tmp'); return path.join(tmpDir, `${solutionId}-values.yaml`); } /** * Check if Helm values file exists for a solution */ export function helmValuesExist(solutionId: string): boolean { return fs.existsSync(getHelmValuesPath(solutionId)); } /** * Ensure tmp directory exists */ export function ensureTmpDir(): string { const tmpDir = path.join(process.cwd(), 'tmp'); if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir, { recursive: true }); } return tmpDir; } /** * Result of Helm command execution */ export interface HelmExecutionResult { success: boolean; output: string; error?: string; } /** * Run Helm dry-run validation */ export async function validateHelmDryRun( chart: HelmChartInfo, releaseName: string, namespace: string, valuesPath: string ): Promise<HelmExecutionResult> { try { await ensureHelmRepo(chart); const dryRunCommand = buildHelmCommand(chart, releaseName, namespace, valuesPath) + ' --dry-run'; const { stdout, stderr } = await executeHelmCommand(dryRunCommand); return { success: true, output: stdout + (stderr ? `\n${stderr}` : '') }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); let cleanError = errorMessage; if (error instanceof Error && 'stderr' in error) { cleanError = (error as any).stderr || errorMessage; } return { success: false, output: '', error: cleanError }; } } /** * Deploy a Helm chart */ export async function deployHelmRelease( chart: HelmChartInfo, releaseName: string, namespace: string, valuesPath: string | undefined, timeout: number ): Promise<HelmExecutionResult> { try { await ensureHelmRepo(chart); const helmCommand = buildHelmCommand(chart, releaseName, namespace, valuesPath) + ` --timeout ${timeout}s --wait`; const { stdout, stderr } = await executeHelmCommand(helmCommand, { timeout: (timeout + 30) * 1000 // Add buffer for command overhead }); return { success: true, output: stdout + (stderr ? `\n\nStderr:\n${stderr}` : '') }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); let cleanError = errorMessage; if (error instanceof Error && 'stderr' in error) { cleanError = (error as any).stderr || errorMessage; } return { success: false, output: '', error: cleanError }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vfarcic/dot-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server