diff --git a/src/books/graceful-physical-cluster-destruction/aws.ts b/src/books/graceful-physical-cluster-destruction/aws.ts index bbaf7f1..d28617f 100644 --- a/src/books/graceful-physical-cluster-destruction/aws.ts +++ b/src/books/graceful-physical-cluster-destruction/aws.ts @@ -9,9 +9,9 @@ import { KubectlUtil, type ResourceSearch } from '../../lib/utility/kubectl-util import type { TaskRunner } from '../../tasks/tasks.ts'; import type { YamlManipulator } from '../../lib/utility/yaml-manipulator.ts'; import type { UtilPrompter } from '../../lib/prompts/util-prompter.ts'; -import type { GracefulClusterCloudDestructor } from './cloud.ts'; +import type { GracefulPhysicalClusterCloudDestructor } from './cloud.ts'; -class AwsGracefulPhysicalClusterDestructor implements GracefulClusterCloudDestructor { +class AwsGracefulPhysicalClusterDestructor implements GracefulPhysicalClusterCloudDestructor { slug: string; constructor( diff --git a/src/books/graceful-physical-cluster-destruction/cloud.ts b/src/books/graceful-physical-cluster-destruction/cloud.ts index b64772c..5537d13 100644 --- a/src/books/graceful-physical-cluster-destruction/cloud.ts +++ b/src/books/graceful-physical-cluster-destruction/cloud.ts @@ -1,9 +1,9 @@ import type { LandscapeCluster } from '../../lib/service-tree-def.ts'; -interface GracefulClusterCloudDestructor { +interface GracefulPhysicalClusterCloudDestructor { slug: string; Run(phy: LandscapeCluster, admin: LandscapeCluster): Promise; } -export type { GracefulClusterCloudDestructor }; +export type { GracefulPhysicalClusterCloudDestructor }; diff --git a/src/books/graceful-physical-cluster-destruction/digital-ocean.ts b/src/books/graceful-physical-cluster-destruction/digital-ocean.ts index e0b55be..1ad65d3 100644 --- a/src/books/graceful-physical-cluster-destruction/digital-ocean.ts +++ b/src/books/graceful-physical-cluster-destruction/digital-ocean.ts @@ -9,9 +9,9 @@ import { KubectlUtil, type ResourceSearch } from '../../lib/utility/kubectl-util import type { TaskRunner } from '../../tasks/tasks.ts'; import type { YamlManipulator } from '../../lib/utility/yaml-manipulator.ts'; import type { UtilPrompter } from '../../lib/prompts/util-prompter.ts'; -import type { GracefulClusterCloudDestructor } from './cloud.ts'; +import type { GracefulPhysicalClusterCloudDestructor } from './cloud.ts'; -class DigitalOceanGracefulPhysicalClusterDestructor implements GracefulClusterCloudDestructor { +class DigitalOceanGracefulPhysicalClusterDestructor implements GracefulPhysicalClusterCloudDestructor { slug: string; constructor( diff --git a/src/books/graceful-physical-cluster-destruction/index.ts b/src/books/graceful-physical-cluster-destruction/index.ts index 98a2752..5ac9379 100644 --- a/src/books/graceful-physical-cluster-destruction/index.ts +++ b/src/books/graceful-physical-cluster-destruction/index.ts @@ -1,7 +1,7 @@ import type { RunBook } from '../run-book.ts'; import type { ServiceTreePrompter } from '../../lib/prompts/landscape.ts'; import type { ServiceTreePrinter } from '../../lib/utility/service-tree-printer.ts'; -import type { GracefulClusterCloudDestructor } from './cloud.ts'; +import type { GracefulPhysicalClusterCloudDestructor } from './cloud.ts'; class GracefulPhysicalClusterDestructor implements RunBook { name: string = 'Graceful Physical Cluster Destruction'; @@ -10,7 +10,7 @@ class GracefulPhysicalClusterDestructor implements RunBook { constructor( private stp: ServiceTreePrompter, private p: ServiceTreePrinter, - private clouds: GracefulClusterCloudDestructor[], + private clouds: GracefulPhysicalClusterCloudDestructor[], ) {} async Run(): Promise { diff --git a/src/books/graceful-physical-cluster-destruction/vultr.ts b/src/books/graceful-physical-cluster-destruction/vultr.ts index be08a3b..75a9653 100644 --- a/src/books/graceful-physical-cluster-destruction/vultr.ts +++ b/src/books/graceful-physical-cluster-destruction/vultr.ts @@ -9,9 +9,9 @@ import { KubectlUtil, type ResourceSearch } from '../../lib/utility/kubectl-util import type { TaskRunner } from '../../tasks/tasks.ts'; import type { YamlManipulator } from '../../lib/utility/yaml-manipulator.ts'; import type { UtilPrompter } from '../../lib/prompts/util-prompter.ts'; -import type { GracefulClusterCloudDestructor } from './cloud.ts'; +import type { GracefulPhysicalClusterCloudDestructor } from './cloud.ts'; -class VultrGracefulPhysicalClusterDestructor implements GracefulClusterCloudDestructor { +class VultrGracefulPhysicalClusterDestructor implements GracefulPhysicalClusterCloudDestructor { slug: string; constructor( diff --git a/src/books/physical-cluster-creation/digital-ocean.ts b/src/books/physical-cluster-creation/digital-ocean.ts index 0018057..6949456 100644 --- a/src/books/physical-cluster-creation/digital-ocean.ts +++ b/src/books/physical-cluster-creation/digital-ocean.ts @@ -128,7 +128,7 @@ class DigitalOceanPhysicalClusterCreator implements PhysicalClusterCloudCreator context: aCtx, namespace: aNs, selector: [ - ['atomi.cloud/sync-wave', 'wave-5'], + ['atomi.cloud/sync-wave', 'wave-4'], ['atomi.cloud/landscape', phyLandscape.slug], ['atomi.cloud/cluster', phyCluster.principal.slug], ], diff --git a/src/books/physical-cluster-migration/index.ts b/src/books/physical-cluster-migration/index.ts new file mode 100644 index 0000000..a17f1c0 --- /dev/null +++ b/src/books/physical-cluster-migration/index.ts @@ -0,0 +1,56 @@ +import type { RunBook } from "../run-book.ts"; +import type { ServiceTreePrompter } from "../../lib/prompts/landscape.ts"; +import type { ServiceTreePrinter } from "../../lib/utility/service-tree-printer.ts"; +import { PhysicalClusterTransitioner } from "./transition.ts"; +import type { PhysicalClusterCloudCreator } from "../physical-cluster-creation/cloud.ts"; +import type { GracefulPhysicalClusterCloudDestructor } from "../graceful-physical-cluster-destruction/cloud.ts"; + +class PhysicalClusterMigrator implements RunBook { + name: string = 'Physical Cluster Migration'; + desc: string = 'Migrate an physical cluster to a new cluster'; + + constructor( + private stp: ServiceTreePrompter, + private printer: ServiceTreePrinter, + private creators: PhysicalClusterCloudCreator[], + private destructors: GracefulPhysicalClusterCloudDestructor[], + private transition: PhysicalClusterTransitioner, + ) {} + + async Run(): Promise { + const [adminLandscape, adminCluster] = await this.stp.AdminLandscapeCluster(); + + const [fromLandscape, fromCluster] = await this.stp.PhysicalLandscapeCluster( + 'Select the physical landscape to migrate from', + 'Select the physical cloud to migrate from', + 'Select the physical cluster to migrate from', + ); + const [toLandscape, toCluster] = await this.stp.PhysicalLandscapeCluster( + 'Select the physical landscape to migrate to', + 'Select the physical cloud to migrate to', + 'Select the physical cluster to migrate to', + ); + + console.log('🎯 Selected Service Tree to migrate'); + this.printer.Print('Admin', [adminLandscape, adminCluster]); + this.printer.Print('From', [fromLandscape, fromCluster]); + this.printer.Print('To ', [toLandscape, toCluster]); + + // select the cloud to create the new cluster in + const c = this.creators.find(x => x.slug === toCluster.cloud.slug); + if (!c) return console.log('⚠️ Cloud not supported'); + + await c.Run([toLandscape, toCluster], [adminLandscape, adminCluster]); + + // perform transition + await this.transition.Run([fromLandscape, fromCluster], [toLandscape, toCluster]); + + // destroy old cluster + const d = this.destructors.find(x => x.slug === fromCluster.cloud.slug); + if (!d) return console.log('⚠️ Cloud not supported'); + + await d.Run([fromLandscape, fromCluster], [adminLandscape, adminCluster]); + } +} + +export { PhysicalClusterMigrator }; diff --git a/src/books/physical-cluster-migration/transition.ts b/src/books/physical-cluster-migration/transition.ts new file mode 100644 index 0000000..94abc63 --- /dev/null +++ b/src/books/physical-cluster-migration/transition.ts @@ -0,0 +1,17 @@ +import type { LandscapeCluster } from '../../lib/service-tree-def.ts'; +import type { TaskRunner } from '../../tasks/tasks.ts'; +import type { LoadBalancerDNSSwitcher } from '../../tasks/lb-dns-switcher.ts'; + +class PhysicalClusterTransitioner { + constructor( + private t: TaskRunner, + private dnsSwitcher: LoadBalancerDNSSwitcher, + ) {} + + async Run([, fC]: LandscapeCluster, [, tC]: LandscapeCluster): Promise { + const task = this.dnsSwitcher.task(fC.principal, tC.principal); + await this.t.Run(task); + } +} + +export { PhysicalClusterTransitioner }; diff --git a/src/books/purge-admin-cluster/cloud.ts b/src/books/purge-admin-cluster/cloud.ts index 027286d..9e9c4ea 100644 --- a/src/books/purge-admin-cluster/cloud.ts +++ b/src/books/purge-admin-cluster/cloud.ts @@ -1,22 +1,19 @@ -import type { LandscapeCluster, ServiceTreeService } from "../../lib/service-tree-def.ts"; -import { $ } from "bun"; -import postgres from "postgres"; -import type { TaskRunner } from "../../tasks/tasks.ts"; +import type { LandscapeCluster, ServiceTreeService } from '../../lib/service-tree-def.ts'; +import { $ } from 'bun'; +import postgres from 'postgres'; +import type { TaskRunner } from '../../tasks/tasks.ts'; class GenericAdminClusterCloudPurger { - constructor( private task: TaskRunner, private projectId: string, private env: string, private tofuKey: string, private kubernetesAccess: ServiceTreeService, - private sulfoxideHelium: ServiceTreeService - ) { - } + private sulfoxideHelium: ServiceTreeService, + ) {} async Run([landscape, cluster]: LandscapeCluster): Promise { - const l = landscape.slug; const c = cluster.principal.slug; @@ -29,57 +26,70 @@ class GenericAdminClusterCloudPurger { const HePID = He.principal.projectId; const HeKey = `${l.toUpperCase()}_${c.toUpperCase()}_KUBECONFIG`; - const rootFlags = `--projectId=${this.projectId} --env=${this.env} ${this.tofuKey}`; const secret = await $`infisical secrets get --plain ${{ raw: rootFlags }}`.text(); // setup connection to tofu const sql = postgres(secret); - await this.task.Run(["Check database reachability", async () => { - try { - await sql`SELECT 1`; - } catch (e) { - console.log("❌ Database not reachable"); - await sql.end(); - throw e; - } - }]); - - await this.task.Run(["Deleting L0 States", async () => { - const table = `${l}-l0-${c}`; - try { - await sql`DELETE FROM ${ sql(table) }.states`; - } catch (e) { - await sql.end(); - throw e; - } - }]); - - await this.task.Run(["Deleting L1 States", async () => { - const table = `${l}-l1-${c}`; - try { - await sql`DELETE FROM ${ sql(table) }.states`; - } catch (e) { - await sql.end(); - throw e; - } - }]); + await this.task.Run([ + 'Check database reachability', + async () => { + try { + await sql`SELECT 1`; + } catch (e) { + console.log('❌ Database not reachable'); + await sql.end(); + throw e; + } + }, + ]); + + await this.task.Run([ + 'Deleting L0 States', + async () => { + const table = `${l}-l0-${c}`; + try { + await sql`DELETE FROM ${sql(table)}.states`; + } catch (e) { + await sql.end(); + throw e; + } + }, + ]); + + await this.task.Run([ + 'Deleting L1 States', + async () => { + const table = `${l}-l1-${c}`; + try { + await sql`DELETE FROM ${sql(table)}.states`; + } catch (e) { + await sql.end(); + throw e; + } + }, + ]); await sql.end(); - await this.task.Run([`Deleting Kubernetes Access for ${l} ${c}`, async () => { - const kaFlag = `--projectId=${KaPID} --env=${landscape.slug} --type=shared ${KaKey}`; - console.log("⛳ Flags: ", kaFlag); - await $`infisical secrets delete ${{ raw: kaFlag }}`; - }]); - - await this.task.Run([`Deleting Sulfoxide Helium for ${l} ${c}`, async () => { - const heFlag = `--projectId=${HePID} --env=${landscape.slug} --type=shared ${HeKey}`; - console.log("⛳ Flags: ", heFlag); - await $`infisical secrets delete ${{ raw: heFlag }}`; - }]); - + await this.task.Run([ + `Deleting Kubernetes Access for ${l} ${c}`, + async () => { + const kaFlag = `--projectId=${KaPID} --env=${landscape.slug} --type=shared ${KaKey}`; + console.log('⛳ Flags: ', kaFlag); + await $`infisical secrets delete ${{ raw: kaFlag }}`; + }, + ]); + + await this.task.Run([ + `Deleting Sulfoxide Helium for ${l} ${c}`, + async () => { + const heFlag = `--projectId=${HePID} --env=${landscape.slug} --type=shared ${HeKey}`; + console.log('⛳ Flags: ', heFlag); + await $`infisical secrets delete ${{ raw: heFlag }}`; + }, + ]); } } diff --git a/src/init/runbooks.ts b/src/init/runbooks.ts index eabd134..c55cdab 100644 --- a/src/init/runbooks.ts +++ b/src/init/runbooks.ts @@ -34,6 +34,8 @@ import { GenericAdminClusterCloudRestorer } from '../books/restore-admin-cluster import { AdminClusterRestorer } from '../books/restore-admin-cluster'; import { VultrBareAdminClusterCreator } from '../books/bare-admin-cluster-creation/vultr.ts'; import { VultrFullAdminClusterCreator } from '../books/full-admin-cluster-creation/vultr.ts'; +import { PhysicalClusterMigrator } from '../books/physical-cluster-migration'; +import { PhysicalClusterTransitioner } from '../books/physical-cluster-migration/transition.ts'; function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { const sulfoxide = SERVICE_TREE.sulfoxide; @@ -71,7 +73,7 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { CLOUDS.AWS.slug, ), ]; - const physicalClusterCreator = new PhysicalClusterCreator( + const phyClusterCreator = new PhysicalClusterCreator( d.taskRunner, d.stp, t.nitrosoWaiter, @@ -115,7 +117,7 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { ), ]; - const phyGracefulDestructor = new GracefulPhysicalClusterDestructor( + const phyClusterGracefulDestructor = new GracefulPhysicalClusterDestructor( d.stp, d.serviceTreePrinter, phyGracefulDestructors, @@ -134,7 +136,17 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { sulfoxide.services.argocd, LANDSCAPE_TREE.v, ); - const phyPurger = new PhysicalClusterPurger(d.stp, d.serviceTreePrinter, genericPhyPurgeCloud); + const phyClusterPurger = new PhysicalClusterPurger(d.stp, d.serviceTreePrinter, genericPhyPurgeCloud); + + // migrate physical cluster + const phyTransitioner = new PhysicalClusterTransitioner(d.taskRunner, t.lbDNSSwitcher); + const phyClusterMigrator = new PhysicalClusterMigrator( + d.stp, + d.serviceTreePrinter, + phyClusterCreators, + phyGracefulDestructors, + phyTransitioner, + ); // bare admin cluster creation const bareAdminCloudCreators: BareAdminClusterCloudCreator[] = [ @@ -159,7 +171,7 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { CLOUDS.Vultr.slug, ), ]; - const bareAdminClusterCreator = new BareAdminClusterCreator(d.stp, d.serviceTreePrinter, bareAdminCloudCreators); + const adminClusterBareCreator = new BareAdminClusterCreator(d.stp, d.serviceTreePrinter, bareAdminCloudCreators); // full admin cluster creation const fullAdminCloudCreators: FullAdminClusterCloudCreator[] = [ @@ -183,7 +195,7 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { ), ]; - const fullAdminCloudCreator = new FullAdminClusterCreator( + const adminClusterFullCreator = new FullAdminClusterCreator( d.stp, d.serviceTreePrinter, bareAdminCloudCreators, @@ -277,13 +289,14 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { return [ // physical cluster - physicalClusterCreator, - phyGracefulDestructor, - phyPurger, + phyClusterCreator, + phyClusterGracefulDestructor, + phyClusterPurger, + phyClusterMigrator, // admin cluster - bareAdminClusterCreator, - fullAdminCloudCreator, + adminClusterBareCreator, + adminClusterFullCreator, adminGracefulDestructor, adminClusterMigrator, purgeAdminCluster, diff --git a/src/tasks/lb-dns-switcher.ts b/src/tasks/lb-dns-switcher.ts index 078c784..2cbfdc9 100755 --- a/src/tasks/lb-dns-switcher.ts +++ b/src/tasks/lb-dns-switcher.ts @@ -23,17 +23,20 @@ class LoadBalancerDNSSwitcher { console.log(`✅ DNS now points to ${target.name}`); await $`sleep 120`; console.log('🔍 Checking if DNS has propagated...'); + const query = async (): Promise => { + console.log(`🖥️ dog pichu.${this.external} -J`); const result = await $`dog pichu.${this.external} -J`.cwd(path).json(); const answers: { name: string; type: string; data: { domain: string } }[] = result.responses[0].answers; return answers.find(x => x.type === 'CNAME')?.data.domain ?? ''; }; - const t = `${target.slug}.${this.internal}`; + const t = `${target.slug}.${this.internal}.`; let domain = await query(); while (domain != t) { console.log(`⏳ Waiting for DNS to propagate...`); await $`sleep 5`; domain = await query(); + console.log(`✅ Check: ${domain}, Target: ${t}`); } console.log(`✅ DNS propagated!`); },