@chimoney.io/iaas-k8s-deployment
Version:
Kubernetes Infrastructure as a Service deployment package for streamlined multi-cloud environments
516 lines ⢠22.3 kB
JavaScript
import { TierCalculator } from "../utils/tier-calculator.js";
import { TierMigrationManager } from "../utils/tier-migration.js";
import { createKubecostClient } from "../utils/kubecost-client.js";
import { PLAN_TIER_DEFINITIONS, DEFAULT_KUBECOST_CONFIG, } from "../types/plans.js";
import chalk from "chalk";
/**
* CLI commands for managing tier-based resource allocation
*/
export class TierCommands {
constructor(options) {
this.options = options;
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() {
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) {
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) => {
console.log(` Error: ${chalk.red(error)}`);
});
}
if (validation.warnings.length > 0) {
validation.warnings.forEach((warning) => {
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) {
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) => {
console.log(` ā ${error}`);
});
}
if (validation.warnings.length > 0) {
console.log(chalk.yellow("\nWarnings:"));
validation.warnings.forEach((warning) => {
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, targetTier) {
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, toTier) {
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, toTier, options = {}) {
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, toTier, options = {}) {
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, toTier) {
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) {
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, logger) {
const defaultLogger = {
info: (message) => console.log(`[INFO] ${message}`),
warn: (message) => console.warn(`[WARN] ${message}`),
error: (message) => console.error(`[ERROR] ${message}`),
debug: (message) => console.debug(`[DEBUG] ${message}`),
};
const commandLogger = logger || defaultLogger;
return yargs.command("tiers", "Manage tier-based resource allocation", (yargs) => yargs
.command("list", "Show available tiers and their specifications", {}, async (args) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.showTiers();
})
.command("calculate <tier>", "Calculate resource allocation for a specific tier", (yargs) => yargs.positional("tier", {
describe: "Plan tier to calculate",
type: "string",
choices: Object.keys(PLAN_TIER_DEFINITIONS),
}), async (args) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.calculateTier(args.tier);
})
.command("validate <tier>", "Validate tier configuration", (yargs) => yargs.positional("tier", {
describe: "Tier to validate",
type: "string",
choices: Object.keys(PLAN_TIER_DEFINITIONS),
}), async (args) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.validateTier(args.tier);
})
.command("migrate <currentTier> <targetTier>", "Migrate resources to a new tier", (yargs) => 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) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.migrateTier(args.currentTier, args.targetTier);
})
.command("plan-migration <fromTier> <toTier>", "Plan a migration from one tier to another", (yargs) => 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) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.planMigration(args.fromTier, args.toTier);
})
.command("execute-migration <fromTier> <toTier>", "Execute a migration from one tier to another", (yargs) => 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) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.executeMigration(args.fromTier, args.toTier, {
dryRun: args.dryRun,
force: args.force,
skipPreChecks: args.skipPreChecks,
});
})
.command("validate-migration <fromTier> <toTier>", "Validate a migration from one tier to another", (yargs) => 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) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.validateMigration(args.fromTier, args.toTier, {
checkResourceUsage: args.checkResourceUsage,
checkClusterCapacity: args.checkClusterCapacity,
});
})
.command("rollback-migration <fromTier> <toTier>", "Rollback a migration from one tier to another", (yargs) => 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) => {
const commands = new TierCommands({
...args,
logger: commandLogger,
});
await commands.rollbackMigration(args.fromTier, args.toTier);
})
.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"));
}
//# sourceMappingURL=tier-commands.js.map