UNPKG

@chimoney.io/iaas-k8s-deployment

Version:

Kubernetes Infrastructure as a Service deployment package for streamlined multi-cloud environments

407 lines (356 loc) 11 kB
import type { Logger, DeploymentProgress, DeploymentResult, } from "../types/index.js"; // ============================================================================= // Metrics and Monitoring // ============================================================================= export interface DeploymentMetrics { deploymentId: string; startTime: Date; endTime?: Date; duration?: number; action: string; stackName: string; companyName: string; success: boolean; errorType?: string; resourcesCreated?: number; resourcesUpdated?: number; resourcesDeleted?: number; rollbackPerformed?: boolean; progressEvents: DeploymentProgress[]; } export interface HealthCheck { name: string; status: "healthy" | "unhealthy" | "unknown"; message: string; timestamp: Date; details?: Record<string, any>; } export class DeploymentMonitor { private metrics: DeploymentMetrics; private logger: Logger; private healthChecks: HealthCheck[] = []; constructor( deploymentId: string, action: string, stackName: string, companyName: string, logger: Logger ) { this.metrics = { deploymentId, startTime: new Date(), action, stackName, companyName, success: false, progressEvents: [], }; this.logger = logger; } recordProgress(progress: DeploymentProgress): void { this.metrics.progressEvents.push(progress); this.logger.info(`Progress: ${progress.status} - ${progress.message}`); } recordCompletion(result: DeploymentResult): void { this.metrics.endTime = new Date(); this.metrics.duration = this.metrics.endTime.getTime() - this.metrics.startTime.getTime(); this.metrics.success = result.success; this.metrics.rollbackPerformed = result.rollbackPerformed; if (!result.success && result.error) { this.metrics.errorType = this.categorizeError(result.error); } if (result.summary) { this.extractResourceChanges(result.summary); } this.logger.info("Deployment metrics recorded", this.getMetricsSummary()); } private categorizeError(error: string): string { if (error.includes("timeout") || error.includes("TIMEOUT")) { return "TIMEOUT"; } if (error.includes("permission") || error.includes("unauthorized")) { return "PERMISSION"; } if (error.includes("validation") || error.includes("invalid")) { return "VALIDATION"; } if (error.includes("network") || error.includes("connection")) { return "NETWORK"; } if (error.includes("resource") && error.includes("conflict")) { return "RESOURCE_CONFLICT"; } return "UNKNOWN"; } private extractResourceChanges(summary: any): void { if (summary.resourceChanges) { this.metrics.resourcesCreated = summary.resourceChanges.create || 0; this.metrics.resourcesUpdated = summary.resourceChanges.update || 0; this.metrics.resourcesDeleted = summary.resourceChanges.delete || 0; } } async performHealthChecks(): Promise<HealthCheck[]> { this.healthChecks = []; // Check Pulumi CLI availability await this.checkPulumiCli(); // Check cloud provider credentials await this.checkCloudCredentials(); // Check Helm chart accessibility await this.checkHelmChart(); // Check network connectivity await this.checkNetworkConnectivity(); return this.healthChecks; } private async checkPulumiCli(): Promise<void> { try { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const { stdout } = await execAsync("pulumi version"); this.addHealthCheck( "pulumi-cli", "healthy", `Pulumi CLI available: ${stdout.trim()}` ); } catch (error) { this.addHealthCheck( "pulumi-cli", "unhealthy", `Pulumi CLI not available: ${ error instanceof Error ? error.message : String(error) }` ); } } private async checkCloudCredentials(): Promise<void> { const cloudProvider = process.env.CLOUD_PROVIDER || "gcp"; try { if (cloudProvider === "aws") { await this.checkAwsCredentials(); } else if (cloudProvider === "gcp") { await this.checkGcpCredentials(); } } catch (error) { this.addHealthCheck( "cloud-credentials", "unhealthy", `Cloud credentials check failed: ${ error instanceof Error ? error.message : String(error) }` ); } } private async checkAwsCredentials(): Promise<void> { try { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const { stdout } = await execAsync("aws sts get-caller-identity"); const identity = JSON.parse(stdout); this.addHealthCheck( "aws-credentials", "healthy", `AWS credentials valid for user: ${identity.UserId}`, { account: identity.Account, arn: identity.Arn } ); } catch (error) { this.addHealthCheck( "aws-credentials", "unhealthy", "AWS credentials not configured or invalid" ); } } private async checkGcpCredentials(): Promise<void> { try { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const { stdout } = await execAsync( 'gcloud auth list --format="value(account)" --filter="status:ACTIVE"' ); const activeAccount = stdout.trim(); if (activeAccount) { this.addHealthCheck( "gcp-credentials", "healthy", `GCP credentials valid for account: ${activeAccount}` ); } else { this.addHealthCheck( "gcp-credentials", "unhealthy", "No active GCP account found" ); } } catch (error) { this.addHealthCheck( "gcp-credentials", "unhealthy", "GCP credentials not configured or invalid" ); } } private async checkHelmChart(): Promise<void> { const helmChartPath = process.env.HELM_CHART_PATH || "../helm-chart"; try { const fs = await import("fs"); const path = await import("path"); const chartYamlPath = path.join(helmChartPath, "Chart.yaml"); if (fs.existsSync(chartYamlPath)) { const chartContent = fs.readFileSync(chartYamlPath, "utf8"); this.addHealthCheck( "helm-chart", "healthy", `Helm chart accessible at: ${helmChartPath}`, { path: chartYamlPath } ); } else { this.addHealthCheck( "helm-chart", "unhealthy", `Helm chart not found at: ${chartYamlPath}` ); } } catch (error) { this.addHealthCheck( "helm-chart", "unhealthy", `Error checking Helm chart: ${ error instanceof Error ? error.message : String(error) }` ); } } private async checkNetworkConnectivity(): Promise<void> { const cloudProvider = process.env.CLOUD_PROVIDER || "gcp"; try { const testUrls = cloudProvider === "aws" ? ["https://aws.amazon.com", "https://s3.amazonaws.com"] : ["https://cloud.google.com", "https://storage.googleapis.com"]; // Simple connectivity check (in a real implementation, you might use actual SDK calls) this.addHealthCheck( "network-connectivity", "healthy", `Network connectivity assumed healthy for ${cloudProvider}` ); } catch (error) { this.addHealthCheck( "network-connectivity", "unhealthy", "Network connectivity issues detected" ); } } private addHealthCheck( name: string, status: HealthCheck["status"], message: string, details?: Record<string, any> ): void { this.healthChecks.push({ name, status, message, timestamp: new Date(), details, }); } getMetrics(): DeploymentMetrics { return { ...this.metrics }; } getMetricsSummary(): object { return { deploymentId: this.metrics.deploymentId, duration: this.metrics.duration, success: this.metrics.success, resourceChanges: { created: this.metrics.resourcesCreated || 0, updated: this.metrics.resourcesUpdated || 0, deleted: this.metrics.resourcesDeleted || 0, }, progressEventCount: this.metrics.progressEvents.length, rollbackPerformed: this.metrics.rollbackPerformed, }; } getHealthStatus(): { overall: "healthy" | "unhealthy"; checks: HealthCheck[]; } { const hasUnhealthy = this.healthChecks.some( (check) => check.status === "unhealthy" ); return { overall: hasUnhealthy ? "unhealthy" : "healthy", checks: this.healthChecks, }; } exportMetrics(): string { return JSON.stringify( { ...this.getMetrics(), healthChecks: this.healthChecks, }, null, 2 ); } } // ============================================================================= // Deployment Reporter // ============================================================================= export class DeploymentReporter { static generateReport( metrics: DeploymentMetrics, healthChecks: HealthCheck[] ): string { const duration = metrics.duration ? `${(metrics.duration / 1000).toFixed(2)}s` : "N/A"; const successIcon = metrics.success ? "✅" : "❌"; let report = ` # Deployment Report ${successIcon} ## Summary - **Deployment ID**: ${metrics.deploymentId} - **Action**: ${metrics.action} - **Stack**: ${metrics.stackName} - **Company**: ${metrics.companyName} - **Status**: ${metrics.success ? "SUCCESS" : "FAILED"} - **Duration**: ${duration} - **Start Time**: ${metrics.startTime.toISOString()} - **End Time**: ${metrics.endTime?.toISOString() || "N/A"} ## Resource Changes - **Created**: ${metrics.resourcesCreated || 0} - **Updated**: ${metrics.resourcesUpdated || 0} - **Deleted**: ${metrics.resourcesDeleted || 0} ## Progress Timeline `; metrics.progressEvents.forEach((event, index) => { report += `${index + 1}. **${event.status}** - ${ event.message } (${event.timestamp.toISOString()})\n`; }); if (metrics.rollbackPerformed) { report += `\n⚠️ **Rollback Performed**: Automatic rollback was executed due to deployment failure.\n`; } if (healthChecks.length > 0) { report += `\n## Health Checks\n`; healthChecks.forEach((check) => { const icon = check.status === "healthy" ? "✅" : check.status === "unhealthy" ? "❌" : "⚠️"; report += `- ${icon} **${check.name}**: ${check.message}\n`; }); } return report; } }