@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
949 lines (845 loc) • 26.3 kB
text/typescript
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,
});
}
}