UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

949 lines (845 loc) 26.3 kB
import { EnvironmentConfig, EnvironmentType, EnvironmentHealth, ComponentHealth, HealthMetrics, InfrastructureProvider, DeploymentStatus, MonitoringConfig, DatabaseConfig, CDNConfig, SSLConfig, AutoScaleConfig, HealthCheckConfig, } from "./types"; export interface EnvironmentProvisioningResult { success: boolean; environmentId: string; resources: ProvisionedResource[]; endpoints: ServiceEndpoint[]; errors?: string[]; warnings?: string[]; duration: number; // seconds cost: number; // estimated monthly cost in USD } export interface ProvisionedResource { id: string; type: string; provider: InfrastructureProvider; status: "creating" | "active" | "updating" | "deleting" | "failed"; endpoint?: string; metadata: Record<string, unknown>; } export interface ServiceEndpoint { name: string; url: string; type: "web" | "api" | "admin" | "websocket"; protocol: "http" | "https" | "ws" | "wss"; health: "healthy" | "degraded" | "unhealthy" | "unknown"; } export interface EnvironmentSnapshot { id: string; environmentId: string; version: string; description: string; configuration: EnvironmentConfig; createdAt: Date; createdBy: string; tags: string[]; } export interface EnvironmentTemplate { id: string; name: string; description: string; type: EnvironmentType; configuration: Partial<EnvironmentConfig>; parameterization: TemplateParameter[]; supportedProviders: InfrastructureProvider[]; category: "development" | "testing" | "staging" | "production" | "custom"; verified: boolean; } export interface TemplateParameter { name: string; type: "string" | "number" | "boolean" | "select" | "multiselect"; description: string; required: boolean; defaultValue?: unknown; options?: { label: string; value: unknown }[]; validation?: { pattern?: string; min?: number; max?: number; minLength?: number; maxLength?: number; }; } export class EnvironmentManager { private environments = new Map<string, EnvironmentConfig>(); private health = new Map<string, EnvironmentHealth>(); private templates = new Map<string, EnvironmentTemplate>(); private monitoring = new Map<string, NodeJS.Timer>(); constructor() { this.initializeDefaultTemplates(); } // Environment Lifecycle Management async createEnvironment( config: Omit<EnvironmentConfig, "id" | "createdAt" | "updatedAt"> ): Promise<EnvironmentProvisioningResult> { const startTime = Date.now(); const environmentId = this.generateId(); try { // Validate configuration await this.validateConfiguration(config); // Provision infrastructure const resources = await this.provisionInfrastructure( environmentId, config ); // Set up monitoring await this.setupEnvironmentMonitoring(environmentId, config.monitoring); // Configure networking const endpoints = await this.configureNetworking(environmentId, config); // Initialize database if required if (config.database) { await this.setupDatabase(environmentId, config.database); } // Set up SSL certificates if (config.ssl.enabled) { await this.setupSSLCertificates(environmentId, config.ssl); } // Configure CDN if enabled if (config.cdn.enabled) { await this.setupCDN(environmentId, config.cdn); } const environment: EnvironmentConfig = { ...config, id: environmentId, createdAt: new Date(), updatedAt: new Date(), }; this.environments.set(environmentId, environment); // Start health monitoring await this.startHealthMonitoring(environmentId); const duration = (Date.now() - startTime) / 1000; const estimatedCost = this.calculateMonthlyCost(config); return { success: true, environmentId, resources, endpoints, duration, cost: estimatedCost, }; } catch (error) { return { success: false, environmentId, resources: [], endpoints: [], errors: [error instanceof Error ? error.message : String(error)], duration: (Date.now() - startTime) / 1000, cost: 0, }; } } async updateEnvironment( environmentId: string, updates: Partial<EnvironmentConfig> ): Promise<EnvironmentConfig> { const environment = this.environments.get(environmentId); if (!environment) { throw new Error(`Environment ${environmentId} not found`); } // Validate updates await this.validateConfigurationUpdates(environment, updates); // Apply infrastructure changes if (this.requiresInfrastructureUpdate(updates)) { await this.updateInfrastructure(environmentId, updates); } // Update configuration const updatedEnvironment: EnvironmentConfig = { ...environment, ...updates, updatedAt: new Date(), }; this.environments.set(environmentId, updatedEnvironment); return updatedEnvironment; } async deleteEnvironment(environmentId: string): Promise<boolean> { const environment = this.environments.get(environmentId); if (!environment) { throw new Error(`Environment ${environmentId} not found`); } if (environment.protected) { throw new Error( `Environment ${environmentId} is protected and cannot be deleted` ); } try { // Stop monitoring this.stopHealthMonitoring(environmentId); // Cleanup infrastructure await this.cleanupInfrastructure(environmentId, environment); // Remove from cache this.environments.delete(environmentId); this.health.delete(environmentId); return true; } catch (error) { console.error(`Failed to delete environment ${environmentId}:`, error); return false; } } // Environment Health and Monitoring async getEnvironmentHealth( environmentId: string ): Promise<EnvironmentHealth> { const cachedHealth = this.health.get(environmentId); if (cachedHealth && this.isHealthDataFresh(cachedHealth)) { return cachedHealth; } const environment = this.environments.get(environmentId); if (!environment) { throw new Error(`Environment ${environmentId} not found`); } const health = await this.checkEnvironmentHealth( environmentId, environment ); this.health.set(environmentId, health); return health; } private async checkEnvironmentHealth( environmentId: string, environment: EnvironmentConfig ): Promise<EnvironmentHealth> { const components: ComponentHealth[] = []; // Check application health if (environment.healthCheck.enabled) { const appHealth = await this.checkApplicationHealth(environment); components.push(appHealth); } // Check database health if (environment.database) { const dbHealth = await this.checkDatabaseHealth(environment.database); components.push(dbHealth); } // Check CDN health if (environment.cdn.enabled) { const cdnHealth = await this.checkCDNHealth(environment.cdn); components.push(cdnHealth); } // Calculate overall health const overall = this.calculateOverallHealth(components); // Get recent deployments const recentDeployments = await this.getRecentDeployments(environmentId); // Collect metrics const metrics = await this.collectHealthMetrics(environmentId); return { environment: environment.type, overall, components, recentDeployments, metrics, lastChecked: new Date(), }; } private async checkApplicationHealth( environment: EnvironmentConfig ): Promise<ComponentHealth> { if (!environment.healthCheck.enabled) { return { name: "Application", type: "application", status: "unhealthy", message: "Health check disabled", lastChecked: new Date(), }; } try { const url = `${environment.healthCheck.protocol}://${environment.domain}${environment.healthCheck.path}`; const response = await fetch(url, { method: "GET", timeout: environment.healthCheck.timeout * 1000, }); if (response.status === environment.healthCheck.expectedStatus) { return { name: "Application", type: "application", status: "healthy", lastChecked: new Date(), }; } else { return { name: "Application", type: "application", status: "unhealthy", message: `Expected status ${environment.healthCheck.expectedStatus}, got ${response.status}`, lastChecked: new Date(), }; } } catch (error) { return { name: "Application", type: "application", status: "unhealthy", message: error instanceof Error ? error.message : String(error), lastChecked: new Date(), }; } } private async checkDatabaseHealth( database: DatabaseConfig ): Promise<ComponentHealth> { try { // Simulate database connection check // In a real implementation, this would use appropriate database client const connectionString = `${database.provider}://${database.username}@${database.host}:${database.port}/${database.database}`; // Mock health check - replace with actual database ping const isHealthy = await this.pingDatabase(database); return { name: "Database", type: "database", status: isHealthy ? "healthy" : "unhealthy", message: isHealthy ? undefined : "Database connection failed", lastChecked: new Date(), }; } catch (error) { return { name: "Database", type: "database", status: "unhealthy", message: error instanceof Error ? error.message : String(error), lastChecked: new Date(), }; } } private async checkCDNHealth(cdn: CDNConfig): Promise<ComponentHealth> { try { // Check CDN origins const healthyOrigins = await Promise.all( cdn.origins.map(async (origin) => { try { const response = await fetch(origin.url, { method: "HEAD", timeout: 5000, }); return response.ok; } catch { return false; } }) ); const healthyCount = healthyOrigins.filter(Boolean).length; const totalCount = cdn.origins.length; if (healthyCount === totalCount) { return { name: "CDN", type: "cdn", status: "healthy", lastChecked: new Date(), }; } else if (healthyCount > 0) { return { name: "CDN", type: "cdn", status: "degraded", message: `${healthyCount}/${totalCount} origins healthy`, lastChecked: new Date(), }; } else { return { name: "CDN", type: "cdn", status: "unhealthy", message: "All origins unavailable", lastChecked: new Date(), }; } } catch (error) { return { name: "CDN", type: "cdn", status: "unhealthy", message: error instanceof Error ? error.message : String(error), lastChecked: new Date(), }; } } // Environment Templates async createEnvironmentFromTemplate( templateId: string, parameters: Record<string, unknown>, overrides?: Partial<EnvironmentConfig> ): Promise<EnvironmentProvisioningResult> { const template = this.templates.get(templateId); if (!template) { throw new Error(`Template ${templateId} not found`); } // Validate parameters this.validateTemplateParameters(template, parameters); // Apply parameters to template const config = this.applyTemplateParameters(template, parameters); // Apply any overrides const finalConfig = { ...config, ...overrides }; return this.createEnvironment(finalConfig); } getEnvironmentTemplates(): EnvironmentTemplate[] { return Array.from(this.templates.values()); } // Environment Snapshots async createSnapshot( environmentId: string, description: string, createdBy: string ): Promise<EnvironmentSnapshot> { const environment = this.environments.get(environmentId); if (!environment) { throw new Error(`Environment ${environmentId} not found`); } const snapshot: EnvironmentSnapshot = { id: this.generateId(), environmentId, version: this.generateVersion(), description, configuration: { ...environment }, createdAt: new Date(), createdBy, tags: [], }; return snapshot; } async restoreFromSnapshot( snapshotId: string ): Promise<EnvironmentProvisioningResult> { // Implementation would restore environment from snapshot throw new Error("Not implemented"); } // Utility Methods private async startHealthMonitoring(environmentId: string): Promise<void> { const environment = this.environments.get(environmentId); if (!environment) return; const interval = setInterval(async () => { try { await this.getEnvironmentHealth(environmentId); } catch (error) { console.error(`Health monitoring failed for ${environmentId}:`, error); } }, environment.healthCheck.interval * 1000); this.monitoring.set(environmentId, interval); } private stopHealthMonitoring(environmentId: string): void { const interval = this.monitoring.get(environmentId); if (interval) { clearInterval(interval); this.monitoring.delete(environmentId); } } private async validateConfiguration( config: Partial<EnvironmentConfig> ): Promise<void> { // Validate required fields if (!config.name || !config.type || !config.provider) { throw new Error("Missing required configuration fields"); } // Validate domain format if ( config.domain && !/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/.test( config.domain ) ) { throw new Error("Invalid domain format"); } // Validate instance configurations if (config.instances) { for (const instance of config.instances) { if (instance.count < 1) { throw new Error("Instance count must be at least 1"); } if (instance.cpu < 0.5 || instance.memory < 1) { throw new Error("Invalid instance resources"); } } } } private async validateConfigurationUpdates( current: EnvironmentConfig, updates: Partial<EnvironmentConfig> ): Promise<void> { // Prevent changing critical fields in production if (current.type === "production") { const protectedFields = ["provider", "region", "type"]; for (const field of protectedFields) { if ( updates[field as keyof EnvironmentConfig] && updates[field as keyof EnvironmentConfig] !== current[field as keyof EnvironmentConfig] ) { throw new Error(`Cannot change ${field} in production environment`); } } } } private requiresInfrastructureUpdate( updates: Partial<EnvironmentConfig> ): boolean { const infraFields = [ "instances", "provider", "region", "autoScale", "database", ]; return infraFields.some((field) => field in updates); } private calculateOverallHealth( components: ComponentHealth[] ): "healthy" | "degraded" | "unhealthy" { if (components.length === 0) return "unknown" as any; const unhealthy = components.filter((c) => c.status === "unhealthy").length; const degraded = components.filter((c) => c.status === "degraded").length; if (unhealthy > 0) return "unhealthy"; if (degraded > 0) return "degraded"; return "healthy"; } private isHealthDataFresh(health: EnvironmentHealth): boolean { const maxAge = 60000; // 1 minute return Date.now() - health.lastChecked.getTime() < maxAge; } private calculateMonthlyCost(config: Partial<EnvironmentConfig>): number { // Simplified cost calculation let cost = 0; if (config.instances) { for (const instance of config.instances) { // Base cost per instance (simplified) const baseCost = 50; // USD per month const cpuCost = instance.cpu * 10; const memoryCost = instance.memory * 5; const storageCost = instance.storage.size * 0.1; cost += (baseCost + cpuCost + memoryCost + storageCost) * instance.count; } } return cost; } private generateId(): string { return `env_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private generateVersion(): string { const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); return `v${timestamp}`; } private validateTemplateParameters( template: EnvironmentTemplate, parameters: Record<string, unknown> ): void { for (const param of template.parameterization) { if (param.required && !(param.name in parameters)) { throw new Error(`Required parameter ${param.name} is missing`); } if (param.name in parameters) { const value = parameters[param.name]; // Type validation if (param.type === "number" && typeof value !== "number") { throw new Error(`Parameter ${param.name} must be a number`); } if (param.type === "boolean" && typeof value !== "boolean") { throw new Error(`Parameter ${param.name} must be a boolean`); } if (param.type === "string" && typeof value !== "string") { throw new Error(`Parameter ${param.name} must be a string`); } // Validation rules if (param.validation) { if (param.validation.pattern && typeof value === "string") { const regex = new RegExp(param.validation.pattern); if (!regex.test(value)) { throw new Error( `Parameter ${param.name} does not match required pattern` ); } } if (typeof value === "number") { if ( param.validation.min !== undefined && value < param.validation.min ) { throw new Error( `Parameter ${param.name} must be at least ${param.validation.min}` ); } if ( param.validation.max !== undefined && value > param.validation.max ) { throw new Error( `Parameter ${param.name} must be at most ${param.validation.max}` ); } } } } } } private applyTemplateParameters( template: EnvironmentTemplate, parameters: Record<string, unknown> ): Partial<EnvironmentConfig> { // Deep clone template configuration const config = JSON.parse(JSON.stringify(template.configuration)); // Apply parameter substitution const jsonString = JSON.stringify(config); let result = jsonString; for (const [key, value] of Object.entries(parameters)) { const placeholder = `{{${key}}}`; result = result.replace(new RegExp(placeholder, "g"), String(value)); } return JSON.parse(result); } // Mock implementations - replace with actual infrastructure providers private async provisionInfrastructure( environmentId: string, config: Partial<EnvironmentConfig> ): Promise<ProvisionedResource[]> { // Mock implementation return [ { id: `${environmentId}_app`, type: "application", provider: config.provider!, status: "active", endpoint: `https://${config.domain}`, metadata: {}, }, ]; } private async setupEnvironmentMonitoring( environmentId: string, monitoring: MonitoringConfig ): Promise<void> { // Mock implementation console.log(`Setting up monitoring for ${environmentId}`); } private async configureNetworking( environmentId: string, config: Partial<EnvironmentConfig> ): Promise<ServiceEndpoint[]> { return [ { name: "Web Application", url: `https://${config.domain}`, type: "web", protocol: "https", health: "healthy", }, ]; } private async setupDatabase( environmentId: string, database: DatabaseConfig ): Promise<void> { console.log(`Setting up database for ${environmentId}`); } private async setupSSLCertificates( environmentId: string, ssl: SSLConfig ): Promise<void> { console.log(`Setting up SSL for ${environmentId}`); } private async setupCDN(environmentId: string, cdn: CDNConfig): Promise<void> { console.log(`Setting up CDN for ${environmentId}`); } private async updateInfrastructure( environmentId: string, updates: Partial<EnvironmentConfig> ): Promise<void> { console.log(`Updating infrastructure for ${environmentId}`); } private async cleanupInfrastructure( environmentId: string, environment: EnvironmentConfig ): Promise<void> { console.log(`Cleaning up infrastructure for ${environmentId}`); } private async pingDatabase(database: DatabaseConfig): Promise<boolean> { // Mock implementation return true; } private async getRecentDeployments(environmentId: string): Promise<any[]> { // Mock implementation return []; } private async collectHealthMetrics( environmentId: string ): Promise<HealthMetrics> { // Mock implementation return { uptime: 99.9, averageResponseTime: 150, errorRate: 0.1, throughput: 1000, }; } private initializeDefaultTemplates(): void { // Development environment template this.templates.set("dev-template", { id: "dev-template", name: "Development Environment", description: "Standard development environment template", type: "development", configuration: { name: "{{environment_name}}", type: "development", provider: "aws", region: "us-east-1", domain: "{{domain_name}}", instances: [ { id: "main", name: "Main Instance", type: "t3.small", count: 1, cpu: 2, memory: 4, storage: { type: "gp3", size: 20, encrypted: true, backupRetention: 7, }, securityGroups: ["default"], tags: {}, }, ], autoScale: { enabled: false, minInstances: 1, maxInstances: 1, cpuThreshold: 70, memoryThreshold: 80, requestThreshold: 1000, scaleUpCooldown: 300, scaleDownCooldown: 300, }, healthCheck: { enabled: true, path: "/health", protocol: "https", port: 443, interval: 30, timeout: 5, healthyThreshold: 2, unhealthyThreshold: 3, expectedStatus: 200, }, }, parameterization: [ { name: "environment_name", type: "string", description: "Name of the environment", required: true, validation: { pattern: "^[a-zA-Z0-9-]+$", minLength: 3, maxLength: 50, }, }, { name: "domain_name", type: "string", description: "Domain name for the environment", required: true, validation: { pattern: "^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\\.[a-zA-Z]{2,}$", }, }, ], supportedProviders: ["aws", "azure", "gcp"], category: "development", verified: true, }); // Production environment template this.templates.set("prod-template", { id: "prod-template", name: "Production Environment", description: "High-availability production environment template", type: "production", configuration: { name: "{{environment_name}}", type: "production", provider: "aws", region: "us-east-1", domain: "{{domain_name}}", protected: true, instances: [ { id: "main", name: "Main Instance", type: "t3.large", count: 2, cpu: 4, memory: 8, storage: { type: "gp3", size: 100, encrypted: true, backupRetention: 30, }, securityGroups: ["production"], tags: { Environment: "production" }, }, ], autoScale: { enabled: true, minInstances: 2, maxInstances: 10, cpuThreshold: 70, memoryThreshold: 80, requestThreshold: 5000, scaleUpCooldown: 300, scaleDownCooldown: 600, }, healthCheck: { enabled: true, path: "/health", protocol: "https", port: 443, interval: 15, timeout: 5, healthyThreshold: 2, unhealthyThreshold: 2, expectedStatus: 200, }, }, parameterization: [ { name: "environment_name", type: "string", description: "Name of the production environment", required: true, validation: { pattern: "^[a-zA-Z0-9-]+$", minLength: 3, maxLength: 50, }, }, { name: "domain_name", type: "string", description: "Production domain name", required: true, validation: { pattern: "^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\\.[a-zA-Z]{2,}$", }, }, ], supportedProviders: ["aws", "azure", "gcp"], category: "production", verified: true, }); } }