UNPKG

@chimoney.io/iaas-k8s-deployment

Version:

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

771 lines (718 loc) • 24 kB
import { TierCalculator } from "../utils/tier-calculator.js"; import { TierMigrationManager } from "../utils/tier-migration.js"; import { createKubecostClient } from "../utils/kubecost-client.js"; import { PlanTier, PLAN_TIER_DEFINITIONS, DEFAULT_KUBECOST_CONFIG, } from "../types/plans.js"; import { Logger } from "../types/index.js"; import chalk from "chalk"; export interface TierCommandOptions { stackName: string; companyName: string; kubeconfig?: string; namespace?: string; kubecostUrl?: string; logger: Logger; } /** * CLI commands for managing tier-based resource allocation */ export class TierCommands { private calculator: TierCalculator; private migrationManager: TierMigrationManager; constructor(private options: TierCommandOptions) { this.calculator = new TierCalculator(options.logger); // Initialize migration manager with Kubecost client const kubecostClient = createKubecostClient( options.kubecostUrl || "http://localhost:9090", DEFAULT_KUBECOST_CONFIG, options.logger ); this.migrationManager = new TierMigrationManager( this.calculator, kubecostClient, options.logger ); } /** * Show available tiers and their resource allocations */ async showTiers(): Promise<void> { console.log(chalk.blue.bold("\nšŸ“Š Available Plan Tiers\n")); Object.entries(PLAN_TIER_DEFINITIONS).forEach(([tier, definition]) => { console.log( chalk.green.bold( `${tier.toUpperCase()} - $${definition.monthlyPriceUSD}/month` ) ); console.log(` Display Name: ${definition.displayName}`); console.log(` Description: ${definition.description}`); console.log(` Max Namespaces: ${definition.maxNamespaces}`); console.log(` Max PVCs: ${definition.maxPersistentVolumeClaims}`); console.log( ` Network Policies: ${ definition.networkPoliciesEnabled ? "Enabled" : "Disabled" }` ); // Service details console.log(` Services:`); Object.entries(definition.services).forEach( ([serviceName, serviceConfig]) => { console.log(` ${serviceName}:`); console.log(` CPU: ${serviceConfig.cpu}`); console.log(` Memory: ${serviceConfig.memory}`); console.log(` Storage: ${serviceConfig.storage}`); console.log(` Replicas: ${serviceConfig.replicas}`); } ); console.log(""); }); } /** * Calculate resource allocation for a specific tier */ async calculateTier(tier: PlanTier): Promise<void> { try { const resources = this.calculator.calculateResources(tier); const validation = this.calculator.validateTierResources(tier, resources); const monthlyCost = this.calculator.calculateMonthlyCost(tier); console.log( chalk.blue.bold( `\nšŸ“‹ Resource Allocation for ${tier.toUpperCase()} Tier\n` ) ); console.log(chalk.yellow("Tier Information:")); console.log(` Display Name: ${resources.displayName}`); console.log(` Description: ${resources.description}`); console.log(` Monthly Cost: $${monthlyCost}`); console.log(` Max Namespaces: ${resources.maxNamespaces}`); console.log(` Max PVCs: ${resources.maxPersistentVolumeClaims}`); console.log( ` Network Policies: ${ resources.networkPoliciesEnabled ? "Enabled" : "Disabled" }\n` ); console.log(chalk.yellow("Service Resource Allocation:")); Object.entries(resources.services).forEach( ([serviceName, serviceConfig]) => { console.log(` ${serviceName}:`); console.log(` CPU: ${serviceConfig.cpu}`); console.log(` Memory: ${serviceConfig.memory}`); console.log(` Storage: ${serviceConfig.storage}`); console.log(` Replicas: ${serviceConfig.replicas}`); } ); console.log(chalk.yellow("\nValidation:")); console.log( ` Valid: ${validation.isValid ? chalk.green("āœ“") : chalk.red("āœ—")}` ); if (!validation.isValid) { validation.errors.forEach((error: string) => { console.log(` Error: ${chalk.red(error)}`); }); } if (validation.warnings.length > 0) { validation.warnings.forEach((warning: string) => { console.log(` Warning: ${chalk.yellow(warning)}`); }); } } catch (error) { console.error(chalk.red(`Error calculating tier: ${error}`)); process.exit(1); } } /** * Validate current tier configuration */ async validateTier(tier: PlanTier): Promise<void> { try { console.log( chalk.blue.bold( `\nšŸ” Validating ${tier.toUpperCase()} Tier Configuration\n` ) ); const resources = this.calculator.calculateResources(tier); const validation = this.calculator.validateTierResources(tier, resources); console.log(chalk.yellow("Resource Validation:")); console.log( ` Status: ${ validation.isValid ? chalk.green("āœ… Valid") : chalk.red("āŒ Invalid") }` ); if (validation.errors.length > 0) { console.log(chalk.red("\nErrors:")); validation.errors.forEach((error: string) => { console.log(` āŒ ${error}`); }); } if (validation.warnings.length > 0) { console.log(chalk.yellow("\nWarnings:")); validation.warnings.forEach((warning: string) => { console.log(` āš ļø ${warning}`); }); } } catch (error) { console.error(chalk.red(`Error validating tier: ${error}`)); process.exit(1); } } /** * Migrate resources to a new tier */ async migrateTier( currentTier: PlanTier, targetTier: PlanTier ): Promise<void> { try { console.log( chalk.blue.bold( `\nšŸš€ Migrating from ${currentTier.toUpperCase()} to ${targetTier.toUpperCase()} Tier\n` ) ); // Perform migration const result = await this.migrationManager.executeMigration( this.options.companyName, currentTier, targetTier, this.options.namespace ); if (result.success) { console.log(chalk.green("Migration successful!")); if (result.duration) { console.log(` Duration: ${result.duration}`); } if (result.details) { console.log( ` Steps Executed: ${result.details.stepsExecuted || "N/A"}` ); if (result.details.costImpact) { const impact = result.details.costImpact; console.log(` Cost Change: $${impact.difference}/month`); } } } else { console.error(chalk.red("Migration failed!")); if (result.error) { console.error(` Error: ${result.error}`); } process.exit(1); } } catch (error) { console.error(chalk.red(`Error during migration: ${error}`)); process.exit(1); } } /** * Plan a tier migration */ async planMigration(fromTier: PlanTier, toTier: PlanTier): Promise<void> { try { console.log( chalk.blue.bold( `\nšŸš€ Planning Migration: ${fromTier.toUpperCase()} → ${toTier.toUpperCase()}\n` ) ); const plan = await this.migrationManager.planMigration( this.options.companyName, fromTier, toTier, this.options.namespace ); console.log(chalk.yellow("šŸ“‹ Migration Plan:")); console.log(` Company: ${plan.companyName}`); console.log(` From Tier: ${plan.fromTier.toUpperCase()}`); console.log(` To Tier: ${plan.toTier.toUpperCase()}`); console.log(` Namespace: ${plan.namespace}`); console.log(` Type: ${plan.migrationType.toUpperCase()}`); console.log(` Estimated Duration: ${plan.estimatedDuration}`); console.log( ` Requires Downtime: ${plan.requiresDowntime ? "Yes" : "No"}` ); if (plan.costImpact) { console.log(chalk.yellow("\nšŸ’° Cost Impact:")); console.log( ` Current Monthly Cost: $${plan.costImpact.currentMonthlyCost}` ); console.log(` New Monthly Cost: $${plan.costImpact.newMonthlyCost}`); const change = plan.costImpact.difference; const changeColor = change > 0 ? chalk.red : change < 0 ? chalk.green : chalk.gray; console.log( ` Difference: ${changeColor( `${change > 0 ? "+" : ""}$${change}/month` )}` ); } if (plan.preChecks.length > 0) { console.log(chalk.yellow("\nāœ… Pre-checks:")); plan.preChecks.forEach((check) => { console.log(` • ${check}`); }); } if (plan.steps.length > 0) { console.log(chalk.yellow("\nšŸ“ Migration Steps:")); plan.steps.forEach((step, index) => { console.log(` ${index + 1}. ${step}`); }); } if (plan.warnings.length > 0) { console.log(chalk.yellow("\nāš ļø Warnings:")); plan.warnings.forEach((warning) => { console.log(` • ${warning}`); }); } if (plan.rollbackSteps.length > 0) { console.log(chalk.yellow("\nšŸ”„ Rollback Steps:")); plan.rollbackSteps.forEach((step, index) => { console.log(` ${index + 1}. ${step}`); }); } } catch (error) { console.error(chalk.red(`Error planning migration: ${error}`)); process.exit(1); } } /** * Execute a tier migration */ async executeMigration( fromTier: PlanTier, toTier: PlanTier, options: { dryRun?: boolean; force?: boolean; skipPreChecks?: boolean; } = {} ): Promise<void> { try { console.log( chalk.blue.bold( `\nšŸš€ ${ options.dryRun ? "Simulating" : "Executing" } Migration: ${fromTier.toUpperCase()} → ${toTier.toUpperCase()}\n` ) ); if (options.dryRun) { console.log( chalk.yellow("šŸ” DRY RUN MODE - No actual changes will be made") ); } const result = await this.migrationManager.executeMigration( this.options.companyName, fromTier, toTier, this.options.namespace, options ); if (result.success) { console.log(chalk.green.bold(`\nāœ… Migration completed successfully!`)); if (result.duration) { console.log(`Duration: ${result.duration}`); } if (result.details) { console.log(chalk.yellow("\nšŸ“Š Migration Details:")); console.log(` From Tier: ${result.details.fromTier?.toUpperCase()}`); console.log(` To Tier: ${result.details.toTier?.toUpperCase()}`); if (result.details.stepsExecuted) { console.log(` Steps Executed: ${result.details.stepsExecuted}`); } if (result.details.costImpact) { const impact = result.details.costImpact; console.log(` Cost Change: $${impact.difference}/month`); } } } else { console.error(chalk.red.bold(`\nāŒ Migration failed!`)); if (result.error) { console.error(`Error: ${result.error}`); } if (result.rollbackRequired) { console.error(chalk.yellow("šŸ”„ Rollback may be required")); } process.exit(1); } } catch (error) { console.error(chalk.red(`Error executing migration: ${error}`)); process.exit(1); } } /** * Validate a tier migration */ async validateMigration( fromTier: PlanTier, toTier: PlanTier, options: { checkResourceUsage?: boolean; checkClusterCapacity?: boolean; } = {} ): Promise<void> { try { console.log( chalk.blue.bold( `\nšŸ” Validating Migration: ${fromTier.toUpperCase()} → ${toTier.toUpperCase()}\n` ) ); const validation = await this.migrationManager.validateMigration( this.options.companyName, fromTier, toTier, this.options.namespace, options ); console.log( `Validation Status: ${ validation.isValid ? chalk.green("āœ… Valid") : chalk.red("āŒ Invalid") }` ); if (validation.errors.length > 0) { console.log(chalk.red("\nāŒ Errors:")); validation.errors.forEach((error) => { console.log(` • ${error}`); }); } if (validation.warnings.length > 0) { console.log(chalk.yellow("\nāš ļø Warnings:")); validation.warnings.forEach((warning) => { console.log(` • ${warning}`); }); } if (validation.recommendations.length > 0) { console.log(chalk.blue("\nšŸ’” Recommendations:")); validation.recommendations.forEach((recommendation) => { console.log(` • ${recommendation}`); }); } if (!validation.isValid) { process.exit(1); } } catch (error) { console.error(chalk.red(`Error validating migration: ${error}`)); process.exit(1); } } /** * Rollback a tier migration */ async rollbackMigration(fromTier: PlanTier, toTier: PlanTier): Promise<void> { try { console.log( chalk.blue.bold( `\nšŸ”„ Rolling back Migration: ${toTier.toUpperCase()} → ${fromTier.toUpperCase()}\n` ) ); const result = await this.migrationManager.rollbackMigration( this.options.companyName, fromTier, toTier, this.options.namespace ); if (result.success) { console.log(chalk.green.bold(`\nāœ… Rollback completed successfully!`)); if (result.duration) { console.log(`Duration: ${result.duration}`); } } else { console.error(chalk.red.bold(`\nāŒ Rollback failed!`)); if (result.error) { console.error(`Error: ${result.error}`); } process.exit(1); } } catch (error) { console.error(chalk.red(`Error rolling back migration: ${error}`)); process.exit(1); } } /** * Show possible migration paths from a tier */ async showMigrationPaths(tier: PlanTier): Promise<void> { try { console.log( chalk.blue.bold( `\nšŸ›¤ļø Migration Paths from ${tier.toUpperCase()} Tier\n` ) ); const paths = this.migrationManager.getPossibleMigrations(tier); if (paths.length === 0) { console.log( chalk.yellow("No migration paths available from this tier.") ); return; } paths.forEach((path) => { const arrow = path.migrationType === "upgrade" ? "ā¬†ļø" : "ā¬‡ļø"; const complexity = path.complexity === "low" ? chalk.green("Low") : path.complexity === "medium" ? chalk.yellow("Medium") : chalk.red("High"); console.log( `${arrow} ${tier.toUpperCase()} → ${path.targetTier.toUpperCase()}` ); console.log(` Type: ${path.migrationType.toUpperCase()}`); console.log(` Complexity: ${complexity}`); console.log(` Estimated Duration: ${path.estimatedDuration}`); console.log(""); }); } catch (error) { console.error(chalk.red(`Error showing migration paths: ${error}`)); process.exit(1); } } } /** * Add tier management commands to yargs */ export function addTierCommands(yargs: any, logger?: Logger) { const defaultLogger: Logger = { info: (message: string) => console.log(`[INFO] ${message}`), warn: (message: string) => console.warn(`[WARN] ${message}`), error: (message: string) => console.error(`[ERROR] ${message}`), debug: (message: string) => console.debug(`[DEBUG] ${message}`), }; const commandLogger = logger || defaultLogger; return yargs.command( "tiers", "Manage tier-based resource allocation", (yargs: any) => yargs .command( "list", "Show available tiers and their specifications", {}, async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.showTiers(); } ) .command( "calculate <tier>", "Calculate resource allocation for a specific tier", (yargs: any) => yargs.positional("tier", { describe: "Plan tier to calculate", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }), async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.calculateTier(args.tier as PlanTier); } ) .command( "validate <tier>", "Validate tier configuration", (yargs: any) => yargs.positional("tier", { describe: "Tier to validate", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }), async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.validateTier(args.tier as PlanTier); } ) .command( "migrate <currentTier> <targetTier>", "Migrate resources to a new tier", (yargs: any) => yargs .positional("currentTier", { describe: "Current plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }) .positional("targetTier", { describe: "Target plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }), async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.migrateTier( args.currentTier as PlanTier, args.targetTier as PlanTier ); } ) .command( "plan-migration <fromTier> <toTier>", "Plan a migration from one tier to another", (yargs: any) => yargs .positional("fromTier", { describe: "Current plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }) .positional("toTier", { describe: "Target plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }), async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.planMigration( args.fromTier as PlanTier, args.toTier as PlanTier ); } ) .command( "execute-migration <fromTier> <toTier>", "Execute a migration from one tier to another", (yargs: any) => yargs .positional("fromTier", { describe: "Current plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }) .positional("toTier", { describe: "Target plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }) .option("dry-run", { describe: "Simulate the migration without making changes", type: "boolean", default: false, }) .option("force", { describe: "Force the migration, skipping confirmation", type: "boolean", default: false, }) .option("skip-pre-checks", { describe: "Skip pre-migration checks", type: "boolean", default: false, }), async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.executeMigration( args.fromTier as PlanTier, args.toTier as PlanTier, { dryRun: args.dryRun, force: args.force, skipPreChecks: args.skipPreChecks, } ); } ) .command( "validate-migration <fromTier> <toTier>", "Validate a migration from one tier to another", (yargs: any) => yargs .positional("fromTier", { describe: "Current plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }) .positional("toTier", { describe: "Target plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }) .option("check-resource-usage", { describe: "Check resource usage for the migration", type: "boolean", default: true, }) .option("check-cluster-capacity", { describe: "Check cluster capacity for the migration", type: "boolean", default: true, }), async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.validateMigration( args.fromTier as PlanTier, args.toTier as PlanTier, { checkResourceUsage: args.checkResourceUsage, checkClusterCapacity: args.checkClusterCapacity, } ); } ) .command( "rollback-migration <fromTier> <toTier>", "Rollback a migration from one tier to another", (yargs: any) => yargs .positional("fromTier", { describe: "Current plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }) .positional("toTier", { describe: "Target plan tier", type: "string", choices: Object.keys(PLAN_TIER_DEFINITIONS), }), async (args: any) => { const commands = new TierCommands({ ...args, logger: commandLogger, }); await commands.rollbackMigration( args.fromTier as PlanTier, args.toTier as PlanTier ); } ) .option("company-name", { describe: "Company name for tier operations", type: "string", demandOption: true, }) .option("stack-name", { describe: "Pulumi stack name", type: "string", demandOption: true, }) .option("namespace", { describe: "Kubernetes namespace (for shared deployments)", type: "string", }) .option("kubeconfig", { describe: "Path to kubeconfig file", type: "string", }) .option("kubecost-url", { describe: "Kubecost server URL", type: "string", }) .demandCommand(1, "Please specify a tier command") ); }