diff --git a/.gitlint b/.gitlint index f87c4d6..57c5506 100644 --- a/.gitlint +++ b/.gitlint @@ -3,4 +3,4 @@ contrib=CT1 ignore=B6 [contrib-title-conventional-commits] -types = amend,build,ci,config,docs,feat,fix,perf,refactor,style,test +types = amend,build,ci,config,docs,feat,fix,perf,refactor,style,test,chore diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..a455290 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,8 @@ +tabWidth: 2 +semi: true +singleQuote: true +bracketSpacing: true +trailingComma: all +arrowParens: avoid +printWidth: 120 +singleAttributePerLine: false diff --git a/Taskfile.yaml b/Taskfile.yaml index f7999b1..3b9e4fb 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -1,4 +1,4 @@ -version: "3" +version: '3' env: SERVICES: >- @@ -29,9 +29,9 @@ tasks: kubectl: desc: Generate kubeconfig from all clusters env: - SLUGS: "pichu pikachu raichu suicune entei" - PCLUSTERS: "entei" - VCLUSTERS: "pichu pikachu raichu" + SLUGS: 'pichu pikachu raichu suicune entei' + PCLUSTERS: 'entei' + VCLUSTERS: 'pichu pikachu raichu' cmds: - ./scripts/local/update-kubectl.sh sync: diff --git a/bun.lockb b/bun.lockb index dcf997e..386d06a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/index.ts b/index.ts index adda69a..0027837 100644 --- a/index.ts +++ b/index.ts @@ -1,8 +1,8 @@ -import { search } from "@inquirer/prompts"; -import { dependencies } from "./src/init"; -import { initTasks } from "./src/init/tasks.ts"; -import { initRunBooks } from "./src/init/runbooks.ts"; -import type { RunBook } from "./src/books/run-book.ts"; +import { search } from '@inquirer/prompts'; +import { dependencies } from './src/init'; +import { initTasks } from './src/init/tasks.ts'; +import { initRunBooks } from './src/init/runbooks.ts'; +import type { RunBook } from './src/books/run-book.ts'; type Choice = { value: Value; @@ -17,13 +17,11 @@ const tasks = initTasks(dependencies); const books = initRunBooks(dependencies, tasks); const book = (await search({ - message: "Which run book do you want to run?", - source: ( - input: string | undefined, - ): Choice[] | Promise[]> => { + message: 'Which run book do you want to run?', + source: (input: string | undefined): Choice[] | Promise[]> => { return books - .filter((b) => b.name.toLowerCase().includes((input ?? "").toLowerCase())) - .map((b) => ({ + .filter(b => b.name.toLowerCase().includes((input ?? '').toLowerCase())) + .map(b => ({ name: b.name, value: b, description: b.desc, @@ -37,9 +35,9 @@ try { if (e != null) { if (e.exitCode) { console.log(`❌ Error running book, exit code: ${e.exitCode}`); - console.log("========= stderr start =========="); + console.log('========= stderr start =========='); console.log(e.stderr.toString()); - console.log("========= stderr end =========="); + console.log('========= stderr end =========='); console.log( Bun.inspect(e, { colors: true, diff --git a/scripts/ci/pre-commit.sh b/scripts/ci/pre-commit.sh index 2ae2958..171a337 100755 --- a/scripts/ci/pre-commit.sh +++ b/scripts/ci/pre-commit.sh @@ -2,6 +2,6 @@ set -eou pipefail -bun i +bun i --frozen-lockfile pre-commit run --all-files -v diff --git a/scripts/local/check.sh b/scripts/local/check.sh index 9c36965..3e1ef7c 100755 --- a/scripts/local/check.sh +++ b/scripts/local/check.sh @@ -13,4 +13,4 @@ for service in ${SERVICES[@]}; do fi done -echo "🔧 Done!" \ No newline at end of file +echo "🔧 Done!" diff --git a/src/books/admin-cluster-migration/index.ts b/src/books/admin-cluster-migration/index.ts index 2b9732d..86baa29 100644 --- a/src/books/admin-cluster-migration/index.ts +++ b/src/books/admin-cluster-migration/index.ts @@ -1,13 +1,13 @@ -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 { BareAdminClusterCloudCreator } from "../bare-admin-cluster-creation/cloud.ts"; -import type { GenericGracefulAdminClusterDestructor } from "../graceful-admin-cluster-destruction/generic.ts"; -import type { AdminClusterTransitioner } from "./transition.ts"; +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 { BareAdminClusterCloudCreator } from '../bare-admin-cluster-creation/cloud.ts'; +import type { GenericGracefulAdminClusterDestructor } from '../graceful-admin-cluster-destruction/generic.ts'; +import type { AdminClusterTransitioner } from './transition.ts'; class AdminClusterMigrator implements RunBook { - name: string = "Admin Cluster Migration"; - desc: string = "Migrate an admin cluster to a new cluster"; + name: string = 'Admin Cluster Migration'; + desc: string = 'Migrate an admin cluster to a new cluster'; constructor( private stp: ServiceTreePrompter, @@ -19,31 +19,28 @@ class AdminClusterMigrator implements RunBook { async Run(): Promise { const [fromLandscape, fromCluster] = await this.stp.AdminLandscapeCluster( - "Select the admin landscape to migrate from", - "Select the admin cloud to migrate from", - "Select the admin cluster to migrate from", + 'Select the admin landscape to migrate from', + 'Select the admin cloud to migrate from', + 'Select the admin cluster to migrate from', ); const [toLandscape, toCluster] = await this.stp.AdminLandscapeCluster( - "Select the admin landscape to migrate to", - "Select the admin cloud to migrate to", - "Select the admin cluster to migrate to", + 'Select the admin landscape to migrate to', + 'Select the admin cloud to migrate to', + 'Select the admin cluster to migrate to', ); - console.log("🎯 Selected Service Tree to migrate"); - this.printer.Print("From", [fromLandscape, fromCluster]); - this.printer.Print("To ", [toLandscape, toCluster]); + console.log('🎯 Selected Service Tree to migrate'); + this.printer.Print('From', [fromLandscape, fromCluster]); + this.printer.Print('To ', [toLandscape, toCluster]); // select the cloud to create the new cluster in - const c = this.clouds.find((x) => x.slug === toCluster.cloud.slug); - if (!c) return console.log("⚠️ Cloud not supported"); + const c = this.clouds.find(x => x.slug === toCluster.cloud.slug); + if (!c) return console.log('⚠️ Cloud not supported'); await c.Run([toLandscape, toCluster]); // perform transition - await this.transition.Run( - [fromLandscape, fromCluster], - [toLandscape, toCluster], - ); + await this.transition.Run([fromLandscape, fromCluster], [toLandscape, toCluster]); // destroy old cluster await this.destructors.Run([fromLandscape, fromCluster]); diff --git a/src/books/admin-cluster-migration/transition.ts b/src/books/admin-cluster-migration/transition.ts index 4b4ddaf..9d6a867 100644 --- a/src/books/admin-cluster-migration/transition.ts +++ b/src/books/admin-cluster-migration/transition.ts @@ -1,12 +1,9 @@ -import type { - LandscapeCluster, - ServiceTreeService, -} from "../../lib/service-tree-def.ts"; -import type { TaskRunner } from "../../tasks/tasks.ts"; -import type { KubectlUtil, Resource } from "../../lib/utility/kubectl-util.ts"; -import { $ } from "bun"; -import type { SulfoxideHeliumWaiter } from "../../tasks/sulfoxide-helium-waiter.ts"; -import type { SulfoxideBoronWaiter } from "../../tasks/sulfoxide-boron-waiter.ts"; +import type { LandscapeCluster, ServiceTreeService } from '../../lib/service-tree-def.ts'; +import type { TaskRunner } from '../../tasks/tasks.ts'; +import type { KubectlUtil, Resource } from '../../lib/utility/kubectl-util.ts'; +import { $ } from 'bun'; +import type { SulfoxideHeliumWaiter } from '../../tasks/sulfoxide-helium-waiter.ts'; +import type { SulfoxideBoronWaiter } from '../../tasks/sulfoxide-boron-waiter.ts'; class AdminClusterTransitioner { constructor( @@ -19,10 +16,7 @@ class AdminClusterTransitioner { private sulfoxideBoronWaiter: SulfoxideBoronWaiter, ) {} - async Run( - [fL, fC]: LandscapeCluster, - [tL, tC]: LandscapeCluster, - ): Promise { + async Run([fL, fC]: LandscapeCluster, [tL, tC]: LandscapeCluster): Promise { // common variables // services @@ -41,7 +35,7 @@ class AdminClusterTransitioner { // dir const F_Dir = `./platforms/${F.platform.slug}/${F.principal.slug}`; - console.log("🎯 Both systems are operational, performing transition..."); + console.log('🎯 Both systems are operational, performing transition...'); const prefix = `${He_NS}-argocd`; const resources: Resource[] = [ ...[ @@ -50,14 +44,14 @@ class AdminClusterTransitioner { `${prefix}-redis`, `${prefix}-server`, `${prefix}-repo-server`, - ].map((name) => ({ - kind: "deployment", + ].map(name => ({ + kind: 'deployment', context: fCtx, namespace: He_NS, name, })), { - kind: "statefulset", + kind: 'statefulset', context: fCtx, namespace: He_NS, name: `${prefix}-application-controller`, @@ -66,17 +60,16 @@ class AdminClusterTransitioner { const replicas: Record = {}; await this.t.Run([ - "Get Replicas", + 'Get Replicas', async () => { - for (const resource of resources) - replicas[resource.name] = await this.k.GetReplica(resource); + for (const resource of resources) replicas[resource.name] = await this.k.GetReplica(resource); }, ]); - console.log("🔀 Replicas before scaling down:", replicas); + console.log('🔀 Replicas before scaling down:', replicas); await this.t.Run([ - "Scale Down Old Cluster", + 'Scale Down Old Cluster', async () => { for (const resource of resources) await this.k.Scale(resource, 0); }, @@ -84,47 +77,45 @@ class AdminClusterTransitioner { // perform migration via velero await this.t.Run([ - "Velero Migration", + 'Velero Migration', async () => { await $`nix develop -c pls migrate -- ${fCtx} ${tCtx}`.cwd(F_Dir); }, ]); await this.t.Exec([ - "Wait for Helium to be migrated", + 'Wait for Helium to be migrated', async () => { await this.k.Wait(5, 5, { - kind: "deployment", + kind: 'deployment', context: tCtx, namespace: He_NS, - selector: [["app.kubernetes.io/part-of", `argocd`]], + selector: [['app.kubernetes.io/part-of', `argocd`]], }); await this.k.Wait(1, 5, { - kind: "statefulset", + kind: 'statefulset', context: tCtx, namespace: He_NS, - selector: [["app.kubernetes.io/part-of", `argocd`]], + selector: [['app.kubernetes.io/part-of', `argocd`]], }); }, ]); await this.t.Exec([ - "Wait for Boron to be migrated", + 'Wait for Boron to be migrated', async () => { await this.k.Wait(1, 5, { - kind: "deployment", + kind: 'deployment', context: tCtx, namespace: B_NS, - fieldSelector: [ - ["metadata.name", `${B.platform.slug}-${B.principal.slug}`], - ], + fieldSelector: [['metadata.name', `${B.platform.slug}-${B.principal.slug}`]], }); }, ]); // Scale back up the new cluster await this.t.Run([ - "Scale Up New Cluster", + 'Scale Up New Cluster', async () => { for (const resource of resources) { resource.context = tCtx; @@ -140,7 +131,7 @@ class AdminClusterTransitioner { const waitForBoron = this.sulfoxideBoronWaiter.task(tCtx, B_NS); await this.t.Run(waitForBoron); - console.log("🎉 Transition completed!"); + console.log('🎉 Transition completed!'); } } diff --git a/src/books/bare-admin-cluster-creation/cloud.ts b/src/books/bare-admin-cluster-creation/cloud.ts index aef62af..07172af 100644 --- a/src/books/bare-admin-cluster-creation/cloud.ts +++ b/src/books/bare-admin-cluster-creation/cloud.ts @@ -1,4 +1,4 @@ -import type { LandscapeCluster } from "../../lib/service-tree-def.ts"; +import type { LandscapeCluster } from '../../lib/service-tree-def.ts'; interface BareAdminClusterCloudCreator { slug: string; diff --git a/src/books/bare-admin-cluster-creation/digital-ocean.ts b/src/books/bare-admin-cluster-creation/digital-ocean.ts index 6b2d652..4a1c4e3 100644 --- a/src/books/bare-admin-cluster-creation/digital-ocean.ts +++ b/src/books/bare-admin-cluster-creation/digital-ocean.ts @@ -1,18 +1,13 @@ -import type { TaskRunner } from "../../tasks/tasks.ts"; -import type { UtilPrompter } from "../../lib/prompts/util-prompter.ts"; -import type { - LandscapeCluster, - ServiceTreeService, -} from "../../lib/service-tree-def.ts"; -import { input } from "@inquirer/prompts"; -import { $ } from "bun"; -import type { BareAdminClusterCloudCreator } from "./cloud.ts"; -import type { KubectlUtil } from "../../lib/utility/kubectl-util.ts"; -import type {SulfoxideXenonWaiter} from "../../tasks/sulfoxide-xenon-waiter.ts"; +import type { TaskRunner } from '../../tasks/tasks.ts'; +import type { UtilPrompter } from '../../lib/prompts/util-prompter.ts'; +import type { LandscapeCluster, ServiceTreeService } from '../../lib/service-tree-def.ts'; +import { input } from '@inquirer/prompts'; +import { $ } from 'bun'; +import type { BareAdminClusterCloudCreator } from './cloud.ts'; +import type { KubectlUtil } from '../../lib/utility/kubectl-util.ts'; +import type { SulfoxideXenonWaiter } from '../../tasks/sulfoxide-xenon-waiter.ts'; -class DigitalOceanBareAdminClusterCreator - implements BareAdminClusterCloudCreator -{ +class DigitalOceanBareAdminClusterCreator implements BareAdminClusterCloudCreator { slug: string; constructor( @@ -40,20 +35,16 @@ class DigitalOceanBareAdminClusterCreator const context = `${admin.landscape.slug}-${admin.cluster.principal.slug}`; // Check if we want to inject the DO secrets - const doSecrets = await this.up.YesNo( - "Do you want to inject Digital Ocean secrets?", - ); + const doSecrets = await this.up.YesNo('Do you want to inject Digital Ocean secrets?'); if (doSecrets) { - const token = await input({ message: "Enter your Digital Ocean token" }); + const token = await input({ message: 'Enter your Digital Ocean token' }); await $`infisical secrets set --projectId=${tofu.principal.projectId} --env=${admin.landscape.slug} ${admin.cluster.principal.slug.toUpperCase()}_DIGITALOCEAN_TOKEN=${token}`; - console.log( - `✅ Digital Ocean secrets for '${admin.landscape.name}' '${admin.cluster.principal.name} injected`, - ); + console.log(`✅ Digital Ocean secrets for '${admin.landscape.name}' '${admin.cluster.principal.name} injected`); } const L0 = `${admin.landscape.slug}:l0:${admin.cluster.principal.slug}`; await this.task.Run([ - "Build L0 Infrastructure", + 'Build L0 Infrastructure', async () => { await $`pls setup`.cwd(tofuDir); await $`pls ${{ raw: L0 }}:init`.cwd(tofuDir); @@ -62,17 +53,15 @@ class DigitalOceanBareAdminClusterCreator ]); await this.task.Run([ - "Retrieve Kubectl Configurations", + 'Retrieve Kubectl Configurations', async () => { await $`pls kubectl`; }, ]); // extract endpoint to use - console.log("📤 Extract endpoint to use..."); - const output = await $`pls ${{ raw: L0 }}:output -- -json` - .cwd(tofuDir) - .json(); + console.log('📤 Extract endpoint to use...'); + const output = await $`pls ${{ raw: L0 }}:output -- -json`.cwd(tofuDir).json(); const endpoint = output.cluster_endpoint.value; console.log(`✅ Extracted endpoint: ${endpoint}`); @@ -80,7 +69,7 @@ class DigitalOceanBareAdminClusterCreator const L1G = `${admin.landscape.slug}:l1:${admin.cluster.set.slug}`; const L1 = `${admin.landscape.slug}:l1:${admin.cluster.principal.slug}`; await this.task.Run([ - "Build L1 Generic Infrastructure", + 'Build L1 Generic Infrastructure', async () => { await $`pls ${{ raw: L1G }}:init`.cwd(tofuDir); await $`pls ${{ raw: L1G }}:apply`.cwd(tofuDir); @@ -89,7 +78,7 @@ class DigitalOceanBareAdminClusterCreator // build L1 infrastructure await this.task.Run([ - "Build L1 Infrastructure", + 'Build L1 Infrastructure', async () => { await $`pls ${{ raw: L1 }}:init`.cwd(tofuDir); await $`pls ${{ raw: L1 }}:apply`.cwd(tofuDir); @@ -98,7 +87,7 @@ class DigitalOceanBareAdminClusterCreator // setup velero backup engine await this.task.Run([ - "Setup Velero Backup Engine", + 'Setup Velero Backup Engine', async () => { await $`nix develop -c pls setup`.cwd(F_Dir); }, @@ -106,7 +95,7 @@ class DigitalOceanBareAdminClusterCreator // install velero backup engine await this.task.Run([ - "Install Velero Backup Engine", + 'Install Velero Backup Engine', async () => { await $`nix develop -c pls velero:${{ raw: admin.landscape.slug }}:install -- --kubecontext ${{ raw: context }}`.cwd( F_Dir, @@ -115,27 +104,27 @@ class DigitalOceanBareAdminClusterCreator ]); await this.task.Exec([ - "Wait for Velero to be ready", + 'Wait for Velero to be ready', async () => { await this.k.WaitForReplica({ - namespace: "velero", - name: "velero", + namespace: 'velero', + name: 'velero', context: context, - kind: "deployment", + kind: 'deployment', }); await $`kubectl --context ${{ raw: context }} -n velero wait --for=jsonpath=.status.phase=Available --timeout=6000s BackupStorageLocation default`; }, ]); await this.task.Run([ - "Deploy Metrics Server", + 'Deploy Metrics Server', async () => { const plsXe = `${landscape.slug}:${cluster.set.slug}`; await $`nix develop -c pls ${{ raw: plsXe }}:install -- --kube-context ${context} -n kube-system`.cwd(Xe_Dir); }, ]); - const xenonWaiter = this.sulfoxideXenonWaiter.task(context, "kube-system"); + const xenonWaiter = this.sulfoxideXenonWaiter.task(context, 'kube-system'); await this.task.Exec(xenonWaiter); } } diff --git a/src/books/bare-admin-cluster-creation/index.ts b/src/books/bare-admin-cluster-creation/index.ts index 39ed2a2..fadfaff 100644 --- a/src/books/bare-admin-cluster-creation/index.ts +++ b/src/books/bare-admin-cluster-creation/index.ts @@ -1,11 +1,11 @@ -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 { BareAdminClusterCloudCreator } from "./cloud.ts"; +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 { BareAdminClusterCloudCreator } from './cloud.ts'; class BareAdminClusterCreator implements RunBook { - name: string = "Bare Admin Cluster Creation"; - desc: string = "Create a empty admin Kubernetes cluster"; + name: string = 'Bare Admin Cluster Creation'; + desc: string = 'Create a empty admin Kubernetes cluster'; constructor( private stp: ServiceTreePrompter, @@ -17,11 +17,11 @@ class BareAdminClusterCreator implements RunBook { const [landscape, cluster] = await this.stp.AdminLandscapeCluster(); // output selected service tree for confirmation - console.log("🎯 Selected Service Tree to create"); - this.printer.Print("", [landscape, cluster]); + console.log('🎯 Selected Service Tree to create'); + this.printer.Print('', [landscape, cluster]); - const c = this.clouds.find((x) => x.slug === cluster.cloud.slug); - if (!c) return console.log("⚠️ Cloud not supported"); + const c = this.clouds.find(x => x.slug === cluster.cloud.slug); + if (!c) return console.log('⚠️ Cloud not supported'); await c.Run([landscape, cluster]); } diff --git a/src/books/full-admin-cluster-creation/cloud.ts b/src/books/full-admin-cluster-creation/cloud.ts index 7a64b99..f6df041 100644 --- a/src/books/full-admin-cluster-creation/cloud.ts +++ b/src/books/full-admin-cluster-creation/cloud.ts @@ -1,4 +1,4 @@ -import type { LandscapeCluster } from "../../lib/service-tree-def.ts"; +import type { LandscapeCluster } from '../../lib/service-tree-def.ts'; interface FullAdminClusterCloudCreator { slug: string; diff --git a/src/books/full-admin-cluster-creation/digital-ocean.ts b/src/books/full-admin-cluster-creation/digital-ocean.ts index 633f5af..0011382 100644 --- a/src/books/full-admin-cluster-creation/digital-ocean.ts +++ b/src/books/full-admin-cluster-creation/digital-ocean.ts @@ -1,16 +1,11 @@ -import { $ } from "bun"; -import type { - LandscapeCluster, - ServiceTreeService, -} from "../../lib/service-tree-def.ts"; -import type { FullAdminClusterCloudCreator } from "./cloud.ts"; -import type { TaskRunner } from "../../tasks/tasks.ts"; -import type { SulfoxideHeliumWaiter } from "../../tasks/sulfoxide-helium-waiter.ts"; -import type { SulfoxideBoronWaiter } from "../../tasks/sulfoxide-boron-waiter.ts"; +import { $ } from 'bun'; +import type { LandscapeCluster, ServiceTreeService } from '../../lib/service-tree-def.ts'; +import type { FullAdminClusterCloudCreator } from './cloud.ts'; +import type { TaskRunner } from '../../tasks/tasks.ts'; +import type { SulfoxideHeliumWaiter } from '../../tasks/sulfoxide-helium-waiter.ts'; +import type { SulfoxideBoronWaiter } from '../../tasks/sulfoxide-boron-waiter.ts'; -class DigitalOceanFullAdminClusterCreator - implements FullAdminClusterCloudCreator -{ +class DigitalOceanFullAdminClusterCreator implements FullAdminClusterCloudCreator { slug: string; constructor( @@ -37,19 +32,17 @@ class DigitalOceanFullAdminClusterCreator const boronNS = `${boron.platform.slug}-${boron.principal.slug}`; await this.task.Run([ - "Create Helium Namespace", + 'Create Helium Namespace', async () => { await $`kubectl create --context ${context} ns ${namespace}`; }, ]); await this.task.Run([ - "Create Helium Helm Release", + 'Create Helium Helm Release', async () => { const heliumPls = `${admin.landscape.slug}:${admin.cluster.set.slug}`; - await $`pls ${{ raw: heliumPls }}:install -- --kube-context ${context} -n ${namespace}`.cwd( - heliumDir, - ); + await $`pls ${{ raw: heliumPls }}:install -- --kube-context ${context} -n ${namespace}`.cwd(heliumDir); }, ]); @@ -57,19 +50,17 @@ class DigitalOceanFullAdminClusterCreator await this.task.Run(waitForHelium); await this.task.Run([ - "Create Boron Namespace", + 'Create Boron Namespace', async () => { await $`kubectl create --context ${context} ns ${boronNS}`; }, ]); await this.task.Run([ - "Create Boron Helm Release", + 'Create Boron Helm Release', async () => { const boronPls = `${admin.landscape.slug}:${admin.cluster.set.slug}`; - await $`pls ${{ raw: boronPls }}:install -- --kube-context ${context} -n ${boronNS}`.cwd( - boronDir, - ); + await $`pls ${{ raw: boronPls }}:install -- --kube-context ${context} -n ${boronNS}`.cwd(boronDir); }, ]); diff --git a/src/books/full-admin-cluster-creation/index.ts b/src/books/full-admin-cluster-creation/index.ts index 5a84ded..0465108 100644 --- a/src/books/full-admin-cluster-creation/index.ts +++ b/src/books/full-admin-cluster-creation/index.ts @@ -1,12 +1,12 @@ -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 { FullAdminClusterCloudCreator } from "./cloud.ts"; -import type { BareAdminClusterCloudCreator } from "../bare-admin-cluster-creation/cloud.ts"; +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 { FullAdminClusterCloudCreator } from './cloud.ts'; +import type { BareAdminClusterCloudCreator } from '../bare-admin-cluster-creation/cloud.ts'; class FullAdminClusterCreator implements RunBook { - name: string = "Full Admin Cluster Creation"; - desc: string = "Create a fully-featured admin Kubernetes cluster"; + name: string = 'Full Admin Cluster Creation'; + desc: string = 'Create a fully-featured admin Kubernetes cluster'; constructor( private stp: ServiceTreePrompter, @@ -19,17 +19,15 @@ class FullAdminClusterCreator implements RunBook { const [landscape, cluster] = await this.stp.AdminLandscapeCluster(); // output selected service tree for confirmation - console.log("🎯 Selected Service Tree to create"); - this.printer.Print("", [landscape, cluster]); + console.log('🎯 Selected Service Tree to create'); + this.printer.Print('', [landscape, cluster]); - const bc = this.bareClouds.find((x) => x.slug === cluster.cloud.slug); - if (!bc) - return console.log("⚠️ Cloud not supported (Missing Bare Cloud Runbook"); + const bc = this.bareClouds.find(x => x.slug === cluster.cloud.slug); + if (!bc) return console.log('⚠️ Cloud not supported (Missing Bare Cloud Runbook'); await bc.Run([landscape, cluster]); - const fc = this.fullClouds.find((x) => x.slug === cluster.cloud.slug); - if (!fc) - return console.log("⚠️ Cloud not supported (Missing Full Cloud Runbook"); + const fc = this.fullClouds.find(x => x.slug === cluster.cloud.slug); + if (!fc) return console.log('⚠️ Cloud not supported (Missing Full Cloud Runbook'); await fc.Run([landscape, cluster]); } } diff --git a/src/books/graceful-admin-cluster-destruction/generic.ts b/src/books/graceful-admin-cluster-destruction/generic.ts index a71ec52..40bea99 100644 --- a/src/books/graceful-admin-cluster-destruction/generic.ts +++ b/src/books/graceful-admin-cluster-destruction/generic.ts @@ -1,10 +1,7 @@ -import type { - LandscapeCluster, - ServiceTreeService, -} from "../../lib/service-tree-def.ts"; -import { $ } from "bun"; -import type { TaskRunner } from "../../tasks/tasks.ts"; -import type { KubectlUtil } from "../../lib/utility/kubectl-util.ts"; +import type { LandscapeCluster, ServiceTreeService } from '../../lib/service-tree-def.ts'; +import { $ } from 'bun'; +import type { TaskRunner } from '../../tasks/tasks.ts'; +import type { KubectlUtil } from '../../lib/utility/kubectl-util.ts'; class GenericGracefulAdminClusterDestructor { constructor( @@ -34,7 +31,7 @@ class GenericGracefulAdminClusterDestructor { // Delete all validating webhooks await this.task.Run([ - "Delete Validating Webhooks", + 'Delete Validating Webhooks', async () => { await $`kubectl --context ${context} delete validatingwebhookconfigurations --all`.nothrow(); }, @@ -42,7 +39,7 @@ class GenericGracefulAdminClusterDestructor { // Delete all namespaces await this.task.Run([ - "Delete Namespaces", + 'Delete Namespaces', async () => { for (const namespace of [heliumNS, boronNS, fluorineNS, mainNS]) { console.log(` 🗑️ Removing namespace: ${namespace}`); @@ -57,7 +54,7 @@ class GenericGracefulAdminClusterDestructor { // setup tofu repository correctly await this.task.Run([ - "Setup Tofu", + 'Setup Tofu', async () => { await $`pls setup`.cwd(tofuDir); }, @@ -66,7 +63,7 @@ class GenericGracefulAdminClusterDestructor { // destroy generic infrastructure const L1G = `${admin.landscape.slug}:l1:${admin.cluster.set.slug}`; await this.task.Run([ - "Destroy Generic Infrastructure", + 'Destroy Generic Infrastructure', async () => { await $`pls ${{ raw: L1G }}:init`.cwd(tofuDir); await $`pls ${{ raw: L1G }}:destroy`.cwd(tofuDir); @@ -76,12 +73,10 @@ class GenericGracefulAdminClusterDestructor { // destroy L1 infrastructure const L1 = `${admin.landscape.slug}:l1:${admin.cluster.principal.slug}`; await this.task.Run([ - "Destroy L1 Infrastructure", + 'Destroy L1 Infrastructure', async () => { await $`pls ${{ raw: L1 }}:init`.cwd(tofuDir); - await $`pls ${{ raw: L1 }}:state:rm -- 'kubernetes_namespace.sulfoxide'` - .cwd(tofuDir) - .nothrow(); + await $`pls ${{ raw: L1 }}:state:rm -- 'kubernetes_namespace.sulfoxide'`.cwd(tofuDir).nothrow(); await $`pls ${{ raw: L1 }}:destroy`.cwd(tofuDir); }, ]); @@ -89,7 +84,7 @@ class GenericGracefulAdminClusterDestructor { // destroy L0 infrastructure const L0 = `${admin.landscape.slug}:l0:${admin.cluster.principal.slug}`; await this.task.Run([ - "Destroy L0 Infrastructure", + 'Destroy L0 Infrastructure', async () => { await $`pls ${{ raw: L0 }}:destroy`.cwd(tofuDir); }, @@ -97,7 +92,7 @@ class GenericGracefulAdminClusterDestructor { // update kubectl configurations await this.task.Run([ - "Retrieve Kubectl Configurations", + 'Retrieve Kubectl Configurations', async () => { await $`pls kubectl`; }, diff --git a/src/books/graceful-admin-cluster-destruction/index.ts b/src/books/graceful-admin-cluster-destruction/index.ts index 6e6befd..8f134c5 100644 --- a/src/books/graceful-admin-cluster-destruction/index.ts +++ b/src/books/graceful-admin-cluster-destruction/index.ts @@ -1,11 +1,11 @@ -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 { GenericGracefulAdminClusterDestructor } from "./generic.ts"; +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 { GenericGracefulAdminClusterDestructor } from './generic.ts'; class GracefulAdminClusterDestructor implements RunBook { - name: string = "Graceful Admin Cluster Destruction"; - desc: string = "Gracefully Destroy a admin Kubernetes cluster"; + name: string = 'Graceful Admin Cluster Destruction'; + desc: string = 'Gracefully Destroy a admin Kubernetes cluster'; constructor( private stp: ServiceTreePrompter, @@ -15,12 +15,11 @@ class GracefulAdminClusterDestructor implements RunBook { async Run(): Promise { // prompt user for physical landscape and cluster - const [adminLandscape, adminCluster] = - await this.stp.AdminLandscapeCluster(); + const [adminLandscape, adminCluster] = await this.stp.AdminLandscapeCluster(); // output selected service tree for confirmation - console.log("🎯 Selected Service Tree for graceful destruction:"); - this.p.Print("", [adminLandscape, adminCluster]); + console.log('🎯 Selected Service Tree for graceful destruction:'); + this.p.Print('', [adminLandscape, adminCluster]); await this.destructor.Run([adminLandscape, adminCluster]); } diff --git a/src/books/graceful-physical-cluster-destruction/generic.ts b/src/books/graceful-physical-cluster-destruction/generic.ts index d4f2ab3..e9bac88 100644 --- a/src/books/graceful-physical-cluster-destruction/generic.ts +++ b/src/books/graceful-physical-cluster-destruction/generic.ts @@ -2,16 +2,13 @@ import type { LandscapeCluster, ServiceTreeLandscapePrincipal, ServiceTreeService, -} from "../../lib/service-tree-def.ts"; -import path from "node:path"; -import { $ } from "bun"; -import { - KubectlUtil, - type ResourceSearch, -} from "../../lib/utility/kubectl-util.ts"; -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"; +} from '../../lib/service-tree-def.ts'; +import path from 'node:path'; +import { $ } from 'bun'; +import { KubectlUtil, type ResourceSearch } from '../../lib/utility/kubectl-util.ts'; +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'; class GenericGracefulPhysicalClusterDestructor { constructor( @@ -39,59 +36,18 @@ class GenericGracefulPhysicalClusterDestructor { const adminNamespaceSlug = `${argo.platform.slug}-${argo.principal.slug}`; const tofuDir = `./platforms/${tofu.platform.slug}/${tofu.principal.slug}`; const heliumDir = `./platforms/${argo.platform.slug}/${argo.principal.slug}`; - const yamlPath = path.join( - heliumDir, - "chart", - `values.${admin.landscape.slug}.${admin.cluster.set.slug}.yaml`, - ); + const yamlPath = path.join(heliumDir, 'chart', `values.${admin.landscape.slug}.${admin.cluster.set.slug}.yaml`); // Update ArgoCD configurations await this.task.Run([ - "Update Helium Configuration", + 'Update Helium Configuration', async () => { console.log(`🗑️ Removing ArgoCD configurations. Path: ${yamlPath}`); await this.y.Mutate(yamlPath, [ - [ - [ - "connector", - "clusters", - phy.landscape.slug, - phy.cluster.principal.slug, - "enable", - ], - false, - ], - [ - [ - "connector", - "clusters", - phy.landscape.slug, - phy.cluster.principal.slug, - "deployAppSet", - ], - false, - ], - [ - [ - "connector", - "clusters", - phy.landscape.slug, - phy.cluster.principal.slug, - "aoa", - "enable", - ], - false, - ], - [ - [ - "connector", - "clusters", - phy.landscape.slug, - phy.cluster.principal.slug, - "destination", - ], - "", - ], + [['connector', 'clusters', phy.landscape.slug, phy.cluster.principal.slug, 'enable'], false], + [['connector', 'clusters', phy.landscape.slug, phy.cluster.principal.slug, 'deployAppSet'], false], + [['connector', 'clusters', phy.landscape.slug, phy.cluster.principal.slug, 'aoa', 'enable'], false], + [['connector', 'clusters', phy.landscape.slug, phy.cluster.principal.slug, 'destination'], ''], ]); }, ]); @@ -99,7 +55,7 @@ class GenericGracefulPhysicalClusterDestructor { // Apply ArgoCD configurations const adminPls = `${admin.landscape.slug}:${admin.cluster.set.slug}`; await this.task.Run([ - "Apply Helium Configuration", + 'Apply Helium Configuration', async () => { await $`pls ${{ raw: adminPls }}:install -- --kube-context ${adminContextSlug} --namespace ${adminNamespaceSlug}`.cwd( heliumDir, @@ -109,35 +65,31 @@ class GenericGracefulPhysicalClusterDestructor { // delete applications from ArgoCD const appsToRemove: ResourceSearch = { - kind: "app", + kind: 'app', context: adminContextSlug, namespace: adminNamespaceSlug, - selector: [["atomi.cloud/cluster", phy.cluster.principal.slug]], + selector: [['atomi.cloud/cluster', phy.cluster.principal.slug]], }; await this.task.Run([ - "Delete Applications", + 'Delete Applications', async () => { return await this.k.Wait(0, 3, appsToRemove, { count: 3, action: async () => { - const deleteApp = await this.up.YesNo( - "Do you want to manually delete the applications?", - ); + const deleteApp = await this.up.YesNo('Do you want to manually delete the applications?'); if (deleteApp) { - console.log( - `🗑️ Delete Root Application: ${phy.landscape.slug}-${phy.cluster.principal.slug}-carbon`, - ); + console.log(`🗑️ Delete Root Application: ${phy.landscape.slug}-${phy.cluster.principal.slug}-carbon`); await this.k.Delete({ - kind: "app", + kind: 'app', context: adminContextSlug, namespace: adminNamespaceSlug, name: `${phy.landscape.slug}-${phy.cluster.principal.slug}-carbon`, }); - console.log("✅ Root Application deleted"); + console.log('✅ Root Application deleted'); - console.log("🗑️ Deleting applications..."); + console.log('🗑️ Deleting applications...'); await this.k.DeleteRange(appsToRemove); - console.log("✅ Applications deleted"); + console.log('✅ Applications deleted'); } return false; }, @@ -147,7 +99,7 @@ class GenericGracefulPhysicalClusterDestructor { // Delete all validating webhooks await this.task.Run([ - "Delete Validating Webhooks", + 'Delete Validating Webhooks', async () => { await $`kubectl --context ${phyContextSlug} delete validatingwebhookconfigurations --all`.nothrow(); }, @@ -155,9 +107,9 @@ class GenericGracefulPhysicalClusterDestructor { // Delete all namespaces await this.task.Run([ - "Delete Namespaces", + 'Delete Namespaces', async () => { - for (const namespace of ["pichu", "pikachu", "raichu", "sulfoxide"]) { + for (const namespace of ['pichu', 'pikachu', 'raichu', 'sulfoxide']) { console.log(` 🗑️ Removing namespace: ${namespace}`); await this.k.DeleteNamespace({ context: phyContextSlug, @@ -170,7 +122,7 @@ class GenericGracefulPhysicalClusterDestructor { // setup tofu repository correctly await this.task.Run([ - "Setup Tofu", + 'Setup Tofu', async () => { await $`pls setup`.cwd(tofuDir); }, @@ -179,7 +131,7 @@ class GenericGracefulPhysicalClusterDestructor { // destroy generic infrastructure const L1G = `${phy.landscape.slug}:l1:${phy.cluster.set.slug}`; await this.task.Run([ - "Destroy Generic Infrastructure", + 'Destroy Generic Infrastructure', async () => { await $`pls ${{ raw: L1G }}:destroy`.cwd(tofuDir); }, @@ -188,11 +140,9 @@ class GenericGracefulPhysicalClusterDestructor { // destroy L1 infrastructure const L1 = `${phy.landscape.slug}:l1:${phy.cluster.principal.slug}`; await this.task.Run([ - "Destroy L1 Infrastructure", + 'Destroy L1 Infrastructure', async () => { - await $`pls ${{ raw: L1 }}:state:rm -- 'kubernetes_namespace.sulfoxide'` - .cwd(tofuDir) - .nothrow(); + await $`pls ${{ raw: L1 }}:state:rm -- 'kubernetes_namespace.sulfoxide'`.cwd(tofuDir).nothrow(); await $`pls ${{ raw: L1 }}:destroy`.cwd(tofuDir); }, ]); @@ -200,7 +150,7 @@ class GenericGracefulPhysicalClusterDestructor { // destroy L0 infrastructure const L0 = `${phy.landscape.slug}:l0:${phy.cluster.principal.slug}`; await this.task.Run([ - "Destroy L0 Infrastructure", + 'Destroy L0 Infrastructure', async () => { await $`pls ${{ raw: L0 }}:state:rm -- 'module.cluster.module.proxy_secret.kubernetes_namespace.kubernetes-access'` .cwd(tofuDir) @@ -211,37 +161,37 @@ class GenericGracefulPhysicalClusterDestructor { // update kubectl configurations await this.task.Run([ - "Retrieve Kubectl Configurations", + 'Retrieve Kubectl Configurations', async () => { await $`pls kubectl`; }, ]); await this.task.Run([ - "Delete ExternalSecret in admin", + 'Delete ExternalSecret in admin', async () => { - for (const ns of this.virtualLandscapes.map((x) => x.slug)) { + for (const ns of this.virtualLandscapes.map(x => x.slug)) { await this.k.Delete({ - kind: "externalsecret", + kind: 'externalsecret', context: adminContextSlug, namespace: adminNamespaceSlug, name: `phase-6-${ns}-${phy.cluster.principal.slug}-cluster-secret`, }); } await this.k.Delete({ - kind: "externalsecret", + kind: 'externalsecret', context: adminContextSlug, namespace: adminNamespaceSlug, name: `${phy.landscape.slug}-${phy.cluster.principal.slug}-external-secret`, }); await this.k.Delete({ - kind: "externalsecret", + kind: 'externalsecret', context: adminContextSlug, namespace: adminNamespaceSlug, name: `${phy.landscape.slug}-${phy.cluster.principal.slug}-external-secret-bearer-token`, }); await this.k.Delete({ - kind: "externalsecret", + kind: 'externalsecret', context: adminContextSlug, namespace: adminNamespaceSlug, name: `${phy.landscape.slug}-${phy.cluster.principal.slug}-external-secret-ca-crt`, @@ -251,11 +201,11 @@ class GenericGracefulPhysicalClusterDestructor { // delete pointers to old cluster in admin await this.task.Run([ - "Delete SecretStore in admin", + 'Delete SecretStore in admin', async () => { - for (const ns of this.virtualLandscapes.map((x) => x.slug)) { + for (const ns of this.virtualLandscapes.map(x => x.slug)) { await this.k.Delete({ - kind: "secretstore", + kind: 'secretstore', context: adminContextSlug, namespace: adminNamespaceSlug, name: `phase-6-${ns}-${phy.cluster.principal.slug}`, diff --git a/src/books/graceful-physical-cluster-destruction/index.ts b/src/books/graceful-physical-cluster-destruction/index.ts index d850092..14df6d9 100644 --- a/src/books/graceful-physical-cluster-destruction/index.ts +++ b/src/books/graceful-physical-cluster-destruction/index.ts @@ -1,11 +1,11 @@ -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 { GenericGracefulPhysicalClusterDestructor } from "./generic.ts"; +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 { GenericGracefulPhysicalClusterDestructor } from './generic.ts'; class GracefulPhysicalClusterDestructor implements RunBook { - name: string = "Graceful Physical Cluster Destruction"; - desc: string = "Gracefully Destroy a physical Kubernetes cluster"; + name: string = 'Graceful Physical Cluster Destruction'; + desc: string = 'Gracefully Destroy a physical Kubernetes cluster'; constructor( private stp: ServiceTreePrompter, @@ -15,18 +15,14 @@ class GracefulPhysicalClusterDestructor implements RunBook { async Run(): Promise { // prompt user for physical landscape and cluster - const [[phyLandscape, phyCluster], [adminLandscape, adminCluster]] = - await this.stp.AdminPhysicalLandscapeCluster(); + const [[phyLandscape, phyCluster], [adminLandscape, adminCluster]] = await this.stp.AdminPhysicalLandscapeCluster(); // output selected service tree for confirmation - console.log("🎯 Selected Service Tree for graceful destruction:"); - this.p.Print("Physical", [phyLandscape, phyCluster]); - this.p.Print("Admin", [adminLandscape, adminCluster]); + console.log('🎯 Selected Service Tree for graceful destruction:'); + this.p.Print('Physical', [phyLandscape, phyCluster]); + this.p.Print('Admin', [adminLandscape, adminCluster]); - await this.destructor.Run( - [phyLandscape, phyCluster], - [adminLandscape, adminCluster], - ); + await this.destructor.Run([phyLandscape, phyCluster], [adminLandscape, adminCluster]); } } diff --git a/src/books/physical-cluster-creation/aws.ts b/src/books/physical-cluster-creation/aws.ts new file mode 100644 index 0000000..08c8f5a --- /dev/null +++ b/src/books/physical-cluster-creation/aws.ts @@ -0,0 +1,258 @@ +import type { PhysicalClusterCloudCreator } from './cloud.ts'; +import { $ } from 'bun'; +import * as path from 'node:path'; +import type { UtilPrompter } from '../../lib/prompts/util-prompter.ts'; +import { input } from '@inquirer/prompts'; +import type { YamlManipulator } from '../../lib/utility/yaml-manipulator.ts'; +import type { KubectlUtil } from '../../lib/utility/kubectl-util.ts'; +import type { LandscapeCluster, ServiceTreeService } from '../../lib/service-tree-def.ts'; +import type { TaskRunner } from '../../tasks/tasks.ts'; +import type { Git } from '../../lib/utility/git.ts'; + +class AwsPhysicalClusterCreator implements PhysicalClusterCloudCreator { + slug: string; + + constructor( + private task: TaskRunner, + private y: YamlManipulator, + private up: UtilPrompter, + private k: KubectlUtil, + private g: Git, + private sulfoxideTofu: ServiceTreeService, + private sulfoxideHelium: ServiceTreeService, + private sulfoxideKrypton: ServiceTreeService, + private sulfoxideLead: ServiceTreeService, + slug: string, + ) { + this.slug = slug; + } + + async Run( + [phyLandscape, phyCluster]: LandscapeCluster, + [adminLandscape, adminCluster]: LandscapeCluster, + ): Promise { + // constants + const tofu = this.sulfoxideTofu; + const He = this.sulfoxideHelium; + const Kr = this.sulfoxideKrypton; + const Pb = this.sulfoxideLead; + + const tofuDir = `./platforms/${tofu.platform.slug}/${tofu.principal.slug}`; + const He_Dir = `./platforms/${He.platform.slug}/${He.principal.slug}`; + const Kr_Dir = `./platforms/${Kr.platform.slug}/${Kr.principal.slug}`; + const Pb_Dir = `./platforms/${Pb.platform.slug}/${Pb.principal.slug}`; + + const He_YamlPath = path.join(He_Dir, 'chart', `values.${adminLandscape.slug}.${adminCluster.set.slug}.yaml`); + const Kr_YamlPath = path.join(Kr_Dir, 'chart', `values.${phyLandscape.slug}.${phyCluster.principal.slug}.yaml`); + const Pb_YamlPath = path.join(Pb_Dir, 'chart', `values.${phyLandscape.slug}.${phyCluster.principal.slug}.yaml`); + + const aCtx = `${adminLandscape.slug}-${adminCluster.principal.slug}`; + const aNS = `${He.platform.slug}-${He.principal.slug}`; + + // Check if we want to inject the DO secrets + const awsSecrets = await this.up.YesNo('Do you want to inject AWS secrets?'); + if (awsSecrets) { + const access = await input({ message: 'Enter your AWS Access Key' }); + await $`infisical secrets set --projectId=${tofu.principal.projectId} --env=${phyLandscape.slug} ${phyCluster.principal.slug.toUpperCase()}_AWS_ACCESS_KEY=${access}`; + console.log('✅ AWS Access Key injected'); + const secret = await input({ message: 'Enter your AWS Secret Key' }); + await $`infisical secrets set --projectId=${tofu.principal.projectId} --env=${phyLandscape.slug} ${phyCluster.principal.slug.toUpperCase()}_AWS_SECRET_KEY=${secret}`; + console.log('✅ AWS Secret Key injected'); + } + + const L0 = `${phyLandscape.slug}:l0:${phyCluster.principal.slug}`; + await this.task.Run([ + 'Build L0 Infrastructure', + async () => { + await $`pls setup`.cwd(tofuDir); + await $`pls ${{ raw: L0 }}:init`.cwd(tofuDir); + await $`pls ${{ raw: L0 }}:apply`.cwd(tofuDir); + }, + ]); + + await this.task.Run([ + 'Retrieve Kubectl Configurations', + async () => { + await $`pls kubectl`; + }, + ]); + + // extract endpoint to use + console.log('📤 Extract endpoint to use...'); + const output = await $`pls ${{ raw: L0 }}:output -- -json`.cwd(tofuDir).json(); + const endpoint = output.cluster_endpoint.value; + console.log(`✅ Extracted endpoint: ${endpoint}`); + + // build L1 generic infrastructure + const L1G = `${phyLandscape.slug}:l1:${phyCluster.set.slug}`; + await this.task.Run([ + 'Build L1 Generic Infrastructure', + async () => { + await $`pls ${{ raw: L1G }}:init`.cwd(tofuDir); + await $`pls ${{ raw: L1G }}:apply`.cwd(tofuDir); + }, + ]); + + // Propagate Tofu outputs for Karpenter + const nodeRole = output.karpenter_node_role_name.value; + const nodeArn = output.karpenter_role_arn.value; + console.log('📤 Extract node role and ARN to use...'); + console.log(`✅ Extracted node role: ${nodeRole}`); + console.log(`✅ Extracted node ARN: ${nodeArn}`); + + await this.task.Run([ + 'Propagate Tofu outputs to Krypton (Karpenter)', + async () => { + console.log(`🛣️ Propagating YAML Path: ${Kr_YamlPath}`); + await this.y.Mutate(Kr_YamlPath, [ + [['nodeRole'], nodeRole], + [['karpenterRole'], nodeArn], + ]); + }, + ]); + + await this.task.Run([ + 'Commit changes to Krypton', + async () => { + await this.g.CommitAndPush(Kr_Dir, 'action: propagate Tofu outputs to Krypton'); + }, + ]); + + // propagate Tofu outputs for Lead + const irsaRoleArn = output.irsa_role_arn.value; + const vpcId = output.vpc_id.value; + console.log('📤 Extract IRSA Role ARN and VPC ID to use...'); + console.log(`✅ Extracted IRSA Role ARN: ${irsaRoleArn}`); + console.log(`✅ Extracted VPC ID: ${vpcId}`); + await this.task.Run([ + 'Propagate Tofu outputs to Lead (IRSA Components)', + async () => { + await this.y.Mutate(Pb_YamlPath, [ + [['role'], irsaRoleArn], + [['vpcId'], vpcId], + ]); + }, + ]); + + await this.task.Run([ + 'Commit changes to Lead', + async () => { + await this.g.CommitAndPush(Pb_Dir, 'action: propagate Tofu outputs to Lead'); + }, + ]); + + // build L1 infrastructure + const L1 = `${phyLandscape.slug}:l1:${phyCluster.principal.slug}`; + await this.task.Run([ + 'Build L1 Infrastructure', + async () => { + await $`pls ${{ raw: L1 }}:init`.cwd(tofuDir); + await $`pls ${{ raw: L1 }}:apply`.cwd(tofuDir); + }, + ]); + + // retrieve yaml in helium folder and replace + await this.task.Run([ + 'Update Helium Configuration', + async () => { + await this.y.Mutate(He_YamlPath, [ + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'enable'], true], + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'deployAppSet'], true], + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'aoa', 'enable'], true], + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'destination'], endpoint], + ]); + }, + ]); + + // apply ArgoCD configurations + const HePls = `${adminLandscape.slug}:${adminCluster.set.slug}`; + await this.task.Run([ + 'Apply Helium Configuration', + async () => { + await $`pls ${{ raw: HePls }}:install -- --kube-context ${aCtx} -n ${aNS}`.cwd(He_Dir); + }, + ]); + + // retrieve kubectl configurations again + await this.task.Run([ + 'Retrieve Kubectl Configurations', + async () => { + await $`pls kubectl`; + }, + ]); + + // wait for iodine to be ready + console.log('🕙 Waiting for iodine to be ready...'); + + await this.task.Exec([ + 'Wait for iodine applications to be ready', + async () => { + await this.k.WaitForApplications(3, { + kind: 'app', + context: aCtx, + namespace: aNS, + selector: [ + ['atomi.cloud/sync-wave', 'wave-5'], + ['atomi.cloud/landscape', phyLandscape.slug], + ['atomi.cloud/cluster', phyCluster.principal.slug], + ], + }); + }, + ]); + + await this.task.Exec([ + 'Wait for statefulset (etcd) to be ready', + async () => { + for (const ns of ['pichu', 'pikachu', 'raichu']) { + await this.k.WaitForReplica({ + kind: 'statefulset', + context: `${phyLandscape.slug}-${phyCluster.principal.slug}`, + namespace: ns, + name: `${phyLandscape.slug}-${ns}-iodine-etcd`, + }); + } + }, + ]); + + await this.task.Exec([ + 'Wait for deployment (iodine) to be ready', + async () => { + for (const ns of ['pichu', 'pikachu', 'raichu']) { + await this.k.WaitForReplica({ + kind: 'deployment', + context: `${phyLandscape.slug}-${phyCluster.principal.slug}`, + namespace: ns, + name: `${phyLandscape.slug}-${ns}-iodine`, + }); + } + }, + ]); + + // retrieve kubectl configurations again + await this.task.Run([ + 'Retrieve Kubectl Configurations', + async () => { + await $`pls kubectl`; + }, + ]); + + // last applications to be ready + await this.task.Exec([ + "Wait for vcluster carbon's last sync wave to be ready", + async () => { + await this.k.WaitForApplications(3, { + kind: 'app', + context: aCtx, + namespace: aNS, + selector: [ + ['atomi.cloud/sync-wave', 'wave-5'], + ['atomi.cloud/element', 'silicon'], + ['atomi.cloud/cluster', phyCluster.principal.slug], + ], + }); + }, + ]); + } +} + +export { AwsPhysicalClusterCreator }; diff --git a/src/books/physical-cluster-creation/cloud.ts b/src/books/physical-cluster-creation/cloud.ts index 668de44..60acc00 100644 --- a/src/books/physical-cluster-creation/cloud.ts +++ b/src/books/physical-cluster-creation/cloud.ts @@ -1,4 +1,4 @@ -import type { LandscapeCluster } from "../../lib/service-tree-def.ts"; +import type { LandscapeCluster } from '../../lib/service-tree-def.ts'; interface PhysicalClusterCloudCreator { slug: string; diff --git a/src/books/physical-cluster-creation/digital-ocean.ts b/src/books/physical-cluster-creation/digital-ocean.ts index 1585472..d12fcf4 100644 --- a/src/books/physical-cluster-creation/digital-ocean.ts +++ b/src/books/physical-cluster-creation/digital-ocean.ts @@ -1,19 +1,14 @@ -import type { PhysicalClusterCloudCreator } from "./cloud.ts"; -import { $ } from "bun"; -import * as path from "node:path"; -import type { UtilPrompter } from "../../lib/prompts/util-prompter.ts"; -import { input } from "@inquirer/prompts"; -import type { YamlManipulator } from "../../lib/utility/yaml-manipulator.ts"; -import type { KubectlUtil } from "../../lib/utility/kubectl-util.ts"; -import type { - LandscapeCluster, - ServiceTreeService, -} from "../../lib/service-tree-def.ts"; -import type { TaskRunner } from "../../tasks/tasks.ts"; - -class DigitalOceanPhysicalClusterCreator - implements PhysicalClusterCloudCreator -{ +import type { PhysicalClusterCloudCreator } from './cloud.ts'; +import { $ } from 'bun'; +import * as path from 'node:path'; +import type { UtilPrompter } from '../../lib/prompts/util-prompter.ts'; +import { input } from '@inquirer/prompts'; +import type { YamlManipulator } from '../../lib/utility/yaml-manipulator.ts'; +import type { KubectlUtil } from '../../lib/utility/kubectl-util.ts'; +import type { LandscapeCluster, ServiceTreeService } from '../../lib/service-tree-def.ts'; +import type { TaskRunner } from '../../tasks/tasks.ts'; + +class DigitalOceanPhysicalClusterCreator implements PhysicalClusterCloudCreator { slug: string; constructor( @@ -38,132 +33,76 @@ class DigitalOceanPhysicalClusterCreator const tofuDir = `./platforms/${tofu.platform.slug}/${tofu.principal.slug}`; const heliumDir = `./platforms/${helium.platform.slug}/${helium.principal.slug}`; - const yamlPath = path.join( - heliumDir, - "chart", - `values.${adminLandscape.slug}.${adminCluster.set.slug}.yaml`, - ); + const yamlPath = path.join(heliumDir, 'chart', `values.${adminLandscape.slug}.${adminCluster.set.slug}.yaml`); const adminContextSlug = `${adminLandscape.slug}-${adminCluster.principal.slug}`; const adminNamespaceSlug = `${helium.platform.slug}-${helium.principal.slug}`; // Check if we want to inject the DO secrets - const doSecrets = await this.up.YesNo( - "Do you want to inject Digital Ocean secrets?", - ); + const doSecrets = await this.up.YesNo('Do you want to inject Digital Ocean secrets?'); if (doSecrets) { - const token = await input({ message: "Enter your Digital Ocean token" }); + const token = await input({ message: 'Enter your Digital Ocean token' }); await $`infisical secrets set --projectId=${tofu.principal.projectId} --env=${phyLandscape.slug} ${phyCluster.principal.slug.toUpperCase()}_DIGITALOCEAN_TOKEN=${token}`; - console.log("✅ Digital Ocean secrets injected"); + console.log('✅ Digital Ocean secrets injected'); } await this.task.Run([ - "Build L0 Infrastructure", + 'Build L0 Infrastructure', async () => { await $`pls setup`.cwd(tofuDir); - await $`pls ${phyLandscape.slug}:l0:${phyCluster.principal.slug}:init`.cwd( - tofuDir, - ); - await $`pls ${phyLandscape.slug}:l0:${phyCluster.principal.slug}:apply`.cwd( - tofuDir, - ); + await $`pls ${phyLandscape.slug}:l0:${phyCluster.principal.slug}:init`.cwd(tofuDir); + await $`pls ${phyLandscape.slug}:l0:${phyCluster.principal.slug}:apply`.cwd(tofuDir); }, ]); await this.task.Run([ - "Retrieve Kubectl Configurations", + 'Retrieve Kubectl Configurations', async () => { await $`pls kubectl`; }, ]); // extract endpoint to use - console.log("📤 Extract endpoint to use..."); - const output = - await $`pls ${phyLandscape.slug}:l0:${phyCluster.principal.slug}:output -- -json` - .cwd(tofuDir) - .json(); + console.log('📤 Extract endpoint to use...'); + const output = await $`pls ${phyLandscape.slug}:l0:${phyCluster.principal.slug}:output -- -json` + .cwd(tofuDir) + .json(); const endpoint = output.cluster_endpoint.value; console.log(`✅ Extracted endpoint: ${endpoint}`); // build L1 generic infrastructure await this.task.Run([ - "Build L1 Generic Infrastructure", + 'Build L1 Generic Infrastructure', async () => { - await $`pls ${phyLandscape.slug}:l1:${phyCluster.set.slug}:init`.cwd( - tofuDir, - ); - await $`pls ${phyLandscape.slug}:l1:${phyCluster.set.slug}:apply`.cwd( - tofuDir, - ); + await $`pls ${phyLandscape.slug}:l1:${phyCluster.set.slug}:init`.cwd(tofuDir); + await $`pls ${phyLandscape.slug}:l1:${phyCluster.set.slug}:apply`.cwd(tofuDir); }, ]); // build L1 infrastructure await this.task.Run([ - "Build L1 Infrastructure", + 'Build L1 Infrastructure', async () => { - await $`pls ${phyLandscape.slug}:l1:${phyCluster.principal.slug}:init`.cwd( - tofuDir, - ); - await $`pls ${phyLandscape.slug}:l1:${phyCluster.principal.slug}:apply`.cwd( - tofuDir, - ); + await $`pls ${phyLandscape.slug}:l1:${phyCluster.principal.slug}:init`.cwd(tofuDir); + await $`pls ${phyLandscape.slug}:l1:${phyCluster.principal.slug}:apply`.cwd(tofuDir); }, ]); // retrieve yaml in helium folder and replace await this.task.Run([ - "Update Helium Configuration", + 'Update Helium Configuration', async () => { await this.y.Mutate(yamlPath, [ - [ - [ - "connector", - "clusters", - phyLandscape.slug, - phyCluster.principal.slug, - "enable", - ], - true, - ], - [ - [ - "connector", - "clusters", - phyLandscape.slug, - phyCluster.principal.slug, - "deployAppSet", - ], - true, - ], - [ - [ - "connector", - "clusters", - phyLandscape.slug, - phyCluster.principal.slug, - "aoa", - "enable", - ], - true, - ], - [ - [ - "connector", - "clusters", - phyLandscape.slug, - phyCluster.principal.slug, - "destination", - ], - endpoint, - ], + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'enable'], true], + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'deployAppSet'], true], + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'aoa', 'enable'], true], + [['connector', 'clusters', phyLandscape.slug, phyCluster.principal.slug, 'destination'], endpoint], ]); }, ]); // apply ArgoCD configurations await this.task.Run([ - "Apply Helium Configuration", + 'Apply Helium Configuration', async () => { await $`pls ${adminLandscape.slug}:${adminCluster.set.slug}:install -- --kube-context ${adminContextSlug} -n ${adminNamespaceSlug}`.cwd( heliumDir, @@ -173,37 +112,37 @@ class DigitalOceanPhysicalClusterCreator // retrieve kubectl configurations again await this.task.Run([ - "Retrieve Kubectl Configurations", + 'Retrieve Kubectl Configurations', async () => { await $`pls kubectl`; }, ]); // wait for iodine to be ready - console.log("🕙 Waiting for iodine to be ready..."); + console.log('🕙 Waiting for iodine to be ready...'); await this.task.Exec([ - "Wait for iodine applications to be ready", + 'Wait for iodine applications to be ready', async () => { await this.k.WaitForApplications(3, { - kind: "app", + kind: 'app', context: adminContextSlug, namespace: adminNamespaceSlug, selector: [ - ["atomi.cloud/sync-wave", "wave-5"], - ["atomi.cloud/landscape", phyLandscape.slug], - ["atomi.cloud/cluster", phyCluster.principal.slug], + ['atomi.cloud/sync-wave', 'wave-5'], + ['atomi.cloud/landscape', phyLandscape.slug], + ['atomi.cloud/cluster', phyCluster.principal.slug], ], }); }, ]); await this.task.Exec([ - "Wait for statefulset (etcd) to be ready", + 'Wait for statefulset (etcd) to be ready', async () => { - for (const ns of ["pichu", "pikachu", "raichu"]) { + for (const ns of ['pichu', 'pikachu', 'raichu']) { await this.k.WaitForReplica({ - kind: "statefulset", + kind: 'statefulset', context: `${phyLandscape.slug}-${phyCluster.principal.slug}`, namespace: ns, name: `${phyLandscape.slug}-${ns}-iodine-etcd`, @@ -213,11 +152,11 @@ class DigitalOceanPhysicalClusterCreator ]); await this.task.Exec([ - "Wait for deployment (iodine) to be ready", + 'Wait for deployment (iodine) to be ready', async () => { - for (const ns of ["pichu", "pikachu", "raichu"]) { + for (const ns of ['pichu', 'pikachu', 'raichu']) { await this.k.WaitForReplica({ - kind: "deployment", + kind: 'deployment', context: `${phyLandscape.slug}-${phyCluster.principal.slug}`, namespace: ns, name: `${phyLandscape.slug}-${ns}-iodine`, @@ -228,7 +167,7 @@ class DigitalOceanPhysicalClusterCreator // retrieve kubectl configurations again await this.task.Run([ - "Retrieve Kubectl Configurations", + 'Retrieve Kubectl Configurations', async () => { await $`pls kubectl`; }, @@ -239,13 +178,13 @@ class DigitalOceanPhysicalClusterCreator "Wait for vcluster carbon's last sync wave to be ready", async () => { await this.k.WaitForApplications(3, { - kind: "app", + kind: 'app', context: adminContextSlug, namespace: adminNamespaceSlug, selector: [ - ["atomi.cloud/sync-wave", "wave-5"], - ["atomi.cloud/element", "silicon"], - ["atomi.cloud/cluster", phyCluster.principal.slug], + ['atomi.cloud/sync-wave', 'wave-5'], + ['atomi.cloud/element', 'silicon'], + ['atomi.cloud/cluster', phyCluster.principal.slug], ], }); }, diff --git a/src/books/physical-cluster-creation/index.ts b/src/books/physical-cluster-creation/index.ts index ad1e269..bfbbd7a 100644 --- a/src/books/physical-cluster-creation/index.ts +++ b/src/books/physical-cluster-creation/index.ts @@ -1,16 +1,13 @@ -import type { TaskRunner } from "../../tasks/tasks.ts"; -import type { RunBook } from "../run-book.ts"; -import type { ServiceTreePrompter } from "../../lib/prompts/landscape.ts"; -import type { PhysicalClusterCloudCreator } from "./cloud.ts"; -import type { NitrosoWaiter } from "../../tasks/nitroso-waiter.ts"; -import type { - ServiceTreeLandscapePrincipal, - ServiceTreeService, -} from "../../lib/service-tree-def.ts"; +import type { TaskRunner } from '../../tasks/tasks.ts'; +import type { RunBook } from '../run-book.ts'; +import type { ServiceTreePrompter } from '../../lib/prompts/landscape.ts'; +import type { PhysicalClusterCloudCreator } from './cloud.ts'; +import type { NitrosoWaiter } from '../../tasks/nitroso-waiter.ts'; +import type { ServiceTreeLandscapePrincipal, ServiceTreeService } from '../../lib/service-tree-def.ts'; class PhysicalClusterCreator implements RunBook { - name: string = "Physical Cluster Creation"; - desc: string = "Create a physical Kubernetes cluster from scratch"; + name: string = 'Physical Cluster Creation'; + desc: string = 'Create a physical Kubernetes cluster from scratch'; constructor( private task: TaskRunner, @@ -27,13 +24,13 @@ class PhysicalClusterCreator implements RunBook { const [phy, admin] = await this.stp.AdminPhysicalLandscapeCluster(); // output selected service tree for confirmation - console.log("🎯 Selected Service Tree to create"); + console.log('🎯 Selected Service Tree to create'); const [phyLandscape, phyCluster] = phy; const [adminLandscape, adminCluster] = admin; - const c = this.clouds.find((x) => x.slug === phyCluster.cloud.slug); - if (!c) return console.log("⚠️ Cloud not supported"); + const c = this.clouds.find(x => x.slug === phyCluster.cloud.slug); + if (!c) return console.log('⚠️ Cloud not supported'); await c.Run(phy, admin); const adminContextSlug = `${adminLandscape.slug}-${adminCluster.principal.slug}`; @@ -42,7 +39,7 @@ class PhysicalClusterCreator implements RunBook { // wait for nitroso to be ready const nitrosoTask = this.nitrosoWaiter.task( phyLandscape.slug, - this.virtualLandscapes.map((x) => x.slug), + this.virtualLandscapes.map(x => x.slug), phyCluster.principal.slug, adminContextSlug, adminNamespaceSlug, diff --git a/src/init/index.ts b/src/init/index.ts index 954ac2e..fe0785c 100644 --- a/src/init/index.ts +++ b/src/init/index.ts @@ -1,11 +1,12 @@ -import { UtilPrompter } from "../lib/prompts/util-prompter.ts"; -import { HttpUtil } from "../lib/utility/http-util.ts"; -import { KubectlUtil } from "../lib/utility/kubectl-util.ts"; -import { ServiceTreePrinter } from "../lib/utility/service-tree-printer.ts"; -import { YamlManipulator } from "../lib/utility/yaml-manipulator.ts"; -import { ServiceTreePrompter } from "../lib/prompts/landscape.ts"; -import { CLOUD_TREE, LANDSCAPE_TREE } from "../lib/service-tree.ts"; -import { TaskRunner } from "../tasks/tasks.ts"; +import { UtilPrompter } from '../lib/prompts/util-prompter.ts'; +import { HttpUtil } from '../lib/utility/http-util.ts'; +import { KubectlUtil } from '../lib/utility/kubectl-util.ts'; +import { ServiceTreePrinter } from '../lib/utility/service-tree-printer.ts'; +import { YamlManipulator } from '../lib/utility/yaml-manipulator.ts'; +import { ServiceTreePrompter } from '../lib/prompts/landscape.ts'; +import { CLOUD_TREE, LANDSCAPE_TREE } from '../lib/service-tree.ts'; +import { TaskRunner } from '../tasks/tasks.ts'; +import { Git } from '../lib/utility/git.ts'; interface Dependencies { httpUtil: HttpUtil; @@ -15,6 +16,7 @@ interface Dependencies { yamlManipulator: YamlManipulator; stp: ServiceTreePrompter; taskRunner: TaskRunner; + git: Git; } // helpers @@ -23,12 +25,9 @@ const utilPrompter = new UtilPrompter(); const kubectl = new KubectlUtil(utilPrompter); const serviceTreePrinter = new ServiceTreePrinter(); const yamlManipulator = new YamlManipulator(); -const stp = new ServiceTreePrompter( - CLOUD_TREE, - LANDSCAPE_TREE.a, - LANDSCAPE_TREE.p, -); +const stp = new ServiceTreePrompter(CLOUD_TREE, LANDSCAPE_TREE.a, LANDSCAPE_TREE.p); const taskRunner = new TaskRunner(utilPrompter); +const git = new Git(); const dependencies: Dependencies = { httpUtil, @@ -38,6 +37,7 @@ const dependencies: Dependencies = { yamlManipulator, stp, taskRunner, + git, }; export { dependencies, type Dependencies }; diff --git a/src/init/runbooks.ts b/src/init/runbooks.ts index f7aabfb..a330897 100644 --- a/src/init/runbooks.ts +++ b/src/init/runbooks.ts @@ -1,22 +1,23 @@ -import type { Dependencies } from "./index.ts"; -import type { TaskGenerator } from "./tasks.ts"; -import type { RunBook } from "../books/run-book.ts"; -import type { PhysicalClusterCloudCreator } from "../books/physical-cluster-creation/cloud.ts"; -import { DigitalOceanPhysicalClusterCreator } from "../books/physical-cluster-creation/digital-ocean.ts"; -import { CLOUDS, LANDSCAPE_TREE, SERVICE_TREE } from "../lib/service-tree.ts"; -import { PhysicalClusterCreator } from "../books/physical-cluster-creation"; -import { GracefulPhysicalClusterDestructor } from "../books/graceful-physical-cluster-destruction"; -import { BareAdminClusterCreator } from "../books/bare-admin-cluster-creation"; -import type { BareAdminClusterCloudCreator } from "../books/bare-admin-cluster-creation/cloud.ts"; -import { DigitalOceanBareAdminClusterCreator } from "../books/bare-admin-cluster-creation/digital-ocean.ts"; -import type { FullAdminClusterCloudCreator } from "../books/full-admin-cluster-creation/cloud.ts"; -import { DigitalOceanFullAdminClusterCreator } from "../books/full-admin-cluster-creation/digital-ocean.ts"; -import { FullAdminClusterCreator } from "../books/full-admin-cluster-creation"; -import { GracefulAdminClusterDestructor } from "../books/graceful-admin-cluster-destruction"; -import { GenericGracefulAdminClusterDestructor } from "../books/graceful-admin-cluster-destruction/generic.ts"; -import { GenericGracefulPhysicalClusterDestructor } from "../books/graceful-physical-cluster-destruction/generic.ts"; -import { AdminClusterMigrator } from "../books/admin-cluster-migration"; -import { AdminClusterTransitioner } from "../books/admin-cluster-migration/transition.ts"; +import type { Dependencies } from './index.ts'; +import type { TaskGenerator } from './tasks.ts'; +import type { RunBook } from '../books/run-book.ts'; +import type { PhysicalClusterCloudCreator } from '../books/physical-cluster-creation/cloud.ts'; +import { DigitalOceanPhysicalClusterCreator } from '../books/physical-cluster-creation/digital-ocean.ts'; +import { CLOUDS, LANDSCAPE_TREE, SERVICE_TREE } from '../lib/service-tree.ts'; +import { PhysicalClusterCreator } from '../books/physical-cluster-creation'; +import { GracefulPhysicalClusterDestructor } from '../books/graceful-physical-cluster-destruction'; +import { BareAdminClusterCreator } from '../books/bare-admin-cluster-creation'; +import type { BareAdminClusterCloudCreator } from '../books/bare-admin-cluster-creation/cloud.ts'; +import { DigitalOceanBareAdminClusterCreator } from '../books/bare-admin-cluster-creation/digital-ocean.ts'; +import type { FullAdminClusterCloudCreator } from '../books/full-admin-cluster-creation/cloud.ts'; +import { DigitalOceanFullAdminClusterCreator } from '../books/full-admin-cluster-creation/digital-ocean.ts'; +import { FullAdminClusterCreator } from '../books/full-admin-cluster-creation'; +import { GracefulAdminClusterDestructor } from '../books/graceful-admin-cluster-destruction'; +import { GenericGracefulAdminClusterDestructor } from '../books/graceful-admin-cluster-destruction/generic.ts'; +import { GenericGracefulPhysicalClusterDestructor } from '../books/graceful-physical-cluster-destruction/generic.ts'; +import { AdminClusterMigrator } from '../books/admin-cluster-migration'; +import { AdminClusterTransitioner } from '../books/admin-cluster-migration/transition.ts'; +import { AwsPhysicalClusterCreator } from '../books/physical-cluster-creation/aws.ts'; function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { const sulfoxide = SERVICE_TREE.sulfoxide; @@ -32,6 +33,18 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { sulfoxide.services.argocd, CLOUDS.DigitalOcean.slug, ), + new AwsPhysicalClusterCreator( + d.taskRunner, + d.yamlManipulator, + d.utilPrompter, + d.kubectl, + d.git, + sulfoxide.services.tofu, + sulfoxide.services.argocd, + sulfoxide.services.cluster_scaler, + sulfoxide.services.aws_adapter, + CLOUDS.AWS.slug, + ), ]; const physicalClusterCreator = new PhysicalClusterCreator( d.taskRunner, @@ -43,16 +56,15 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { ); // graceful physical cluster destruction - const genericPhyGracefulDestructor = - new GenericGracefulPhysicalClusterDestructor( - d.taskRunner, - d.yamlManipulator, - d.kubectl, - d.utilPrompter, - sulfoxide.services.tofu, - sulfoxide.services.argocd, - LANDSCAPE_TREE.v, - ); + const genericPhyGracefulDestructor = new GenericGracefulPhysicalClusterDestructor( + d.taskRunner, + d.yamlManipulator, + d.kubectl, + d.utilPrompter, + sulfoxide.services.tofu, + sulfoxide.services.argocd, + LANDSCAPE_TREE.v, + ); const phyGracefulDestructor = new GracefulPhysicalClusterDestructor( d.stp, @@ -73,11 +85,7 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { CLOUDS.DigitalOcean.slug, ), ]; - const bareAdminClusterCreator = new BareAdminClusterCreator( - d.stp, - d.serviceTreePrinter, - bareAdminCloudCreators, - ); + const bareAdminClusterCreator = new BareAdminClusterCreator(d.stp, d.serviceTreePrinter, bareAdminCloudCreators); // full admin cluster creation const fullAdminCloudCreators: FullAdminClusterCloudCreator[] = [ @@ -99,14 +107,13 @@ function initRunBooks(d: Dependencies, t: TaskGenerator): RunBook[] { ); // graceful admin cluster destruction - const genericAdminGracefulDestructor = - new GenericGracefulAdminClusterDestructor( - d.taskRunner, - d.kubectl, - sulfoxide.services.argocd, - sulfoxide.services.internal_ingress, - sulfoxide.services.tofu, - ); + const genericAdminGracefulDestructor = new GenericGracefulAdminClusterDestructor( + d.taskRunner, + d.kubectl, + sulfoxide.services.argocd, + sulfoxide.services.internal_ingress, + sulfoxide.services.tofu, + ); const adminGracefulDestructor = new GracefulAdminClusterDestructor( d.stp, diff --git a/src/init/tasks.ts b/src/init/tasks.ts index 910e709..5ff373e 100644 --- a/src/init/tasks.ts +++ b/src/init/tasks.ts @@ -1,9 +1,9 @@ -import { NitrosoWaiter } from "../tasks/nitroso-waiter.ts"; -import type { Dependencies } from "./index.ts"; -import { SulfoxideHeliumWaiter } from "../tasks/sulfoxide-helium-waiter.ts"; -import { SulfoxideBoronWaiter } from "../tasks/sulfoxide-boron-waiter.ts"; -import { SERVICE_TREE } from "../lib/service-tree.ts"; -import {SulfoxideXenonWaiter} from "../tasks/sulfoxide-xenon-waiter.ts"; +import { NitrosoWaiter } from '../tasks/nitroso-waiter.ts'; +import type { Dependencies } from './index.ts'; +import { SulfoxideHeliumWaiter } from '../tasks/sulfoxide-helium-waiter.ts'; +import { SulfoxideBoronWaiter } from '../tasks/sulfoxide-boron-waiter.ts'; +import { SERVICE_TREE } from '../lib/service-tree.ts'; +import { SulfoxideXenonWaiter } from '../tasks/sulfoxide-xenon-waiter.ts'; interface TaskGenerator { nitrosoWaiter: NitrosoWaiter; @@ -16,18 +16,9 @@ function initTasks(d: Dependencies): TaskGenerator { const services = SERVICE_TREE.sulfoxide.services; return { nitrosoWaiter: new NitrosoWaiter(d.kubectl, d.httpUtil), - sulfoxideHeliumWaiter: new SulfoxideHeliumWaiter( - d.kubectl, - services.argocd, - ), - sulfoxideBoronWaiter: new SulfoxideBoronWaiter( - d.kubectl, - services.internal_ingress, - ), - sulfoxideXenonWaiter: new SulfoxideXenonWaiter( - d.kubectl, - services.metricsServer, - ), + sulfoxideHeliumWaiter: new SulfoxideHeliumWaiter(d.kubectl, services.argocd), + sulfoxideBoronWaiter: new SulfoxideBoronWaiter(d.kubectl, services.internal_ingress), + sulfoxideXenonWaiter: new SulfoxideXenonWaiter(d.kubectl, services.metricsServer), }; } diff --git a/src/lib/prompts/landscape.ts b/src/lib/prompts/landscape.ts index 2ae486b..f42b2a6 100644 --- a/src/lib/prompts/landscape.ts +++ b/src/lib/prompts/landscape.ts @@ -1,11 +1,11 @@ -import { select } from "@inquirer/prompts"; +import { select } from '@inquirer/prompts'; import type { LandscapeCluster, CloudTreeCloud, CloudTreeCluster, ServiceTreeLandscapePrincipal, CloudTreeClusterSet, -} from "../service-tree-def.ts"; +} from '../service-tree-def.ts'; class ServiceTreePrompter { constructor( @@ -19,20 +19,17 @@ class ServiceTreePrompter { prompt?: string, ): Promise { return (await select({ - message: prompt ?? "Select landscape", - choices: landscapes.map((x) => ({ + message: prompt ?? 'Select landscape', + choices: landscapes.map(x => ({ name: x.name, value: x, })), })) as ServiceTreeLandscapePrincipal; } - async Cluster( - cloudPrompt?: string, - clusterPrompt?: string, - ): Promise { + async Cluster(cloudPrompt?: string, clusterPrompt?: string): Promise { const cloud: CloudTreeCloud = (await select({ - message: cloudPrompt ?? "Select cloud provider", + message: cloudPrompt ?? 'Select cloud provider', choices: Object.entries(this.cloudTree).map(([name, c]) => ({ name, value: c, @@ -40,14 +37,11 @@ class ServiceTreePrompter { })), })) as CloudTreeCloud; - const clusterSets = Object.values( - this.cloudTree[cloud.principal.name].clusterSets, - ); + const clusterSets = Object.values(this.cloudTree[cloud.principal.name].clusterSets); const clusterSet = (await select({ - message: - clusterPrompt ?? `Select cluster set under '${cloud.principal.name}'`, - choices: clusterSets.map((x) => ({ + message: clusterPrompt ?? `Select cluster set under '${cloud.principal.name}'`, + choices: clusterSets.map(x => ({ name: x.principal.name, value: x, description: x.principal.description, @@ -57,10 +51,8 @@ class ServiceTreePrompter { const clusters = Object.values(clusterSet.clusters); return (await select({ - message: - clusterPrompt ?? - `Select cluster under '${cloud.principal.name} {${clusterSet.principal.name}}'`, - choices: clusters.map((x) => ({ + message: clusterPrompt ?? `Select cluster under '${cloud.principal.name} {${clusterSet.principal.name}}'`, + choices: clusters.map(x => ({ name: x.principal.name, value: x, description: x.principal.description, @@ -76,16 +68,8 @@ class ServiceTreePrompter { adminCloudPrompt?: string, adminClusterPrompt?: string, ): Promise<[LandscapeCluster, LandscapeCluster]> { - const phy = await this.PhysicalLandscapeCluster( - phyLandscapePrompt, - phyCloudPrompt, - phyClusterPrompt, - ); - const admin = await this.AdminLandscapeCluster( - adminLandscapePrompt, - adminCloudPrompt, - adminClusterPrompt, - ); + const phy = await this.PhysicalLandscapeCluster(phyLandscapePrompt, phyCloudPrompt, phyClusterPrompt); + const admin = await this.AdminLandscapeCluster(adminLandscapePrompt, adminCloudPrompt, adminClusterPrompt); return [phy, admin]; } @@ -96,9 +80,9 @@ class ServiceTreePrompter { ): Promise { return await this.LandscapeCluster( this.phyLandscapes, - phyLandscapePrompt ?? "Select physical landscape to use", - phyCloudPrompt ?? "Select physical cloud provider to use", - phyClusterPrompt ?? "Select physical cluster key to use", + phyLandscapePrompt ?? 'Select physical landscape to use', + phyCloudPrompt ?? 'Select physical cloud provider to use', + phyClusterPrompt ?? 'Select physical cluster key to use', ); } @@ -109,9 +93,9 @@ class ServiceTreePrompter { ): Promise { return await this.LandscapeCluster( this.adminLandscapes, - landscapePrompt ?? "Select admin landscape to use", - cloudPrompt ?? "Select admin cloud provider to use", - clusterPrompt ?? "Select admin cluster key to use", + landscapePrompt ?? 'Select admin landscape to use', + cloudPrompt ?? 'Select admin cloud provider to use', + clusterPrompt ?? 'Select admin cluster key to use', ); } diff --git a/src/lib/prompts/util-prompter.ts b/src/lib/prompts/util-prompter.ts index ae500cd..f3b1467 100644 --- a/src/lib/prompts/util-prompter.ts +++ b/src/lib/prompts/util-prompter.ts @@ -1,26 +1,26 @@ -import { select } from "@inquirer/prompts"; +import { select } from '@inquirer/prompts'; class UtilPrompter { async YesNo(question: string): Promise { const cont = (await select({ message: question, choices: [ - { name: "Yes", value: true }, - { name: "No", value: false }, + { name: 'Yes', value: true }, + { name: 'No', value: false }, ], })) as boolean; return cont; } - async YesNoExit(question: string): Promise { - const cont = (await select({ + async YesNoExit(question: string): Promise { + const cont = (await select({ message: question, choices: [ - { name: "Yes", value: true }, - { name: "No", value: false }, - { name: "Exit", value: "exit" }, + { name: 'Yes', value: true }, + { name: 'No', value: false }, + { name: 'Exit', value: 'exit' }, ], - })) as boolean | "exit"; + })) as boolean | 'exit'; return cont; } } diff --git a/src/lib/service-tree.ts b/src/lib/service-tree.ts index d0f3d0a..9601e52 100644 --- a/src/lib/service-tree.ts +++ b/src/lib/service-tree.ts @@ -6,213 +6,213 @@ import type { ServiceTreeLandscapePrincipal, ServiceTreePlatform, ServiceTreePlatformPrincipal, -} from "./service-tree-def.ts"; +} from './service-tree-def.ts'; const LANDSCAPES = { arceus: { - name: "Arceus", - slug: "arceus", - description: "Global meta landscape-agnostic administrative environment", + name: 'Arceus', + slug: 'arceus', + description: 'Global meta landscape-agnostic administrative environment', }, pinsir: { - name: "Pinsir", - slug: "pinsir", - description: "Continuous Integration", + name: 'Pinsir', + slug: 'pinsir', + description: 'Continuous Integration', }, lapras: { - name: "Lapras", - slug: "lapras", - description: "Local development", + name: 'Lapras', + slug: 'lapras', + description: 'Local development', }, tauros: { - name: "Tauros", - slug: "tauros", - description: "Local Development (Production Parity)", + name: 'Tauros', + slug: 'tauros', + description: 'Local Development (Production Parity)', }, absol: { - name: "Absol", - slug: "absol", - description: "Test environment", + name: 'Absol', + slug: 'absol', + description: 'Test environment', }, pichu: { - name: "Pichu", - slug: "pichu", - description: "Singapore Development Environment", + name: 'Pichu', + slug: 'pichu', + description: 'Singapore Development Environment', }, pikachu: { - name: "Pikachu", - slug: "pikachu", - description: "Singapore Staging Environment", + name: 'Pikachu', + slug: 'pikachu', + description: 'Singapore Staging Environment', }, raichu: { - name: "Raichu", - slug: "raichu", - description: "Singapore Production Environment", + name: 'Raichu', + slug: 'raichu', + description: 'Singapore Production Environment', }, suicune: { - name: "Suicune", - slug: "suicune", - description: "Singapore administrative environment", + name: 'Suicune', + slug: 'suicune', + description: 'Singapore administrative environment', }, entei: { - name: "Entei", - slug: "entei", - description: "Singapore physical landscape", + name: 'Entei', + slug: 'entei', + description: 'Singapore physical landscape', }, } satisfies Record; const CLOUDS = { DigitalOcean: { - name: "DigitalOcean", - slug: "digitalocean", - description: "DigitalOcean Cloud", + name: 'DigitalOcean', + slug: 'digitalocean', + description: 'DigitalOcean Cloud', }, Linode: { - name: "Linode", - slug: "linode", - description: "Linode Cloud, Akamai", + name: 'Linode', + slug: 'linode', + description: 'Linode Cloud, Akamai', }, Vultr: { - name: "Vultr", - slug: "vultr", - description: "Vultr Cloud", + name: 'Vultr', + slug: 'vultr', + description: 'Vultr Cloud', }, AWS: { - name: "AWS", - slug: "aws", - description: "AWS Cloud, Amazon Web Services", + name: 'AWS', + slug: 'aws', + description: 'AWS Cloud, Amazon Web Services', }, GCP: { - name: "GCP", - slug: "gcp", - description: "GCP Cloud, Google Cloud Platform", + name: 'GCP', + slug: 'gcp', + description: 'GCP Cloud, Google Cloud Platform', }, Azure: { - name: "Azure", - slug: "azure", - description: "Azure Cloud, Microsoft Azure", + name: 'Azure', + slug: 'azure', + description: 'Azure Cloud, Microsoft Azure', }, } satisfies Record; const CLUSTER_SETS = { OpalRuby: { - name: "OpalRuby", - slug: "opal-ruby", - description: "Opal-Ruby Cluster Set", + name: 'OpalRuby', + slug: 'opal-ruby', + description: 'Opal-Ruby Cluster Set', }, OnyxJade: { - name: "OnyxJade", - slug: "onyx-jade", - description: "Onyx-Jade Cluster Set", + name: 'OnyxJade', + slug: 'onyx-jade', + description: 'Onyx-Jade Cluster Set', }, MicaTalc: { - name: "MicaTalc", - slug: "mica-talc", - description: "Mica-Talc Cluster Set", + name: 'MicaTalc', + slug: 'mica-talc', + description: 'Mica-Talc Cluster Set', }, TopazAmber: { - name: "TopazAmber", - slug: "topaz-amber", - description: "Topaz-Amber Cluster Set", + name: 'TopazAmber', + slug: 'topaz-amber', + description: 'Topaz-Amber Cluster Set', }, AgateLapis: { - name: "AgateLapis", - slug: "agate-lapis", - description: "Agate-Lapis Cluster Set", + name: 'AgateLapis', + slug: 'agate-lapis', + description: 'Agate-Lapis Cluster Set', }, BerylCoral: { - name: "BerylCoral", - slug: "beryl-coral", - description: "Beryl-Coral Cluster Set", + name: 'BerylCoral', + slug: 'beryl-coral', + description: 'Beryl-Coral Cluster Set', }, } satisfies Record; const CLUSTERS = { Opal: { - name: "Opal", - slug: "opal", - description: "Opal Cluster", + name: 'Opal', + slug: 'opal', + description: 'Opal Cluster', }, Ruby: { - name: "Ruby", - slug: "ruby", - description: "Ruby Cluster", + name: 'Ruby', + slug: 'ruby', + description: 'Ruby Cluster', }, Onyx: { - name: "Onyx", - slug: "onyx", - description: "Onyx Cluster", + name: 'Onyx', + slug: 'onyx', + description: 'Onyx Cluster', }, Jade: { - name: "Jade", - slug: "jade", - description: "Jade Cluster", + name: 'Jade', + slug: 'jade', + description: 'Jade Cluster', }, Mica: { - name: "mica", - slug: "mica", - description: "Mica Cluster", + name: 'mica', + slug: 'mica', + description: 'Mica Cluster', }, Talc: { - name: "Talc", - slug: "talc", - description: "Talc Cluster", + name: 'Talc', + slug: 'talc', + description: 'Talc Cluster', }, Topaz: { - name: "Topaz", - slug: "topaz", - description: "Topaz Cluster", + name: 'Topaz', + slug: 'topaz', + description: 'Topaz Cluster', }, Amber: { - name: "Amber", - slug: "amber", - description: "Amber Cluster", + name: 'Amber', + slug: 'amber', + description: 'Amber Cluster', }, Agate: { - name: "Agate", - slug: "agate", - description: "Agate Cluster", + name: 'Agate', + slug: 'agate', + description: 'Agate Cluster', }, Lapis: { - name: "Lapis", - slug: "lapis", - description: "Lapis Cluster", + name: 'Lapis', + slug: 'lapis', + description: 'Lapis Cluster', }, Beryl: { - name: "Beryl", - slug: "beryl", - description: "Beryl Cluster", + name: 'Beryl', + slug: 'beryl', + description: 'Beryl Cluster', }, Coral: { - name: "Coral", - slug: "coral", - description: "Coral Cluster", + name: 'Coral', + slug: 'coral', + description: 'Coral Cluster', }, } satisfies Record; const PLATFORMS = { Sulfoxide: { - name: "Sulfoxide", - slug: "sulfoxide", - description: "System and infrastructure components", + name: 'Sulfoxide', + slug: 'sulfoxide', + description: 'System and infrastructure components', }, Nitroso: { - name: "Nitroso", - slug: "nitroso", - description: "BunnyBooker", + name: 'Nitroso', + slug: 'nitroso', + description: 'BunnyBooker', }, Azo: { - name: "Azo", - slug: "azo", - description: "Romantic Song Composer", + name: 'Azo', + slug: 'azo', + description: 'Romantic Song Composer', }, } satisfies Record; @@ -221,174 +221,183 @@ const SERVICE_TREE = { sulfoxide: { principal: PLATFORMS.Sulfoxide, services: { + aws_adapter: { + principal: { + name: 'AWS Adapter', + slug: 'lead', + description: 'AWS Adapters, like CSI and ELB controller', + projectId: '', + }, + platform: PLATFORMS.Sulfoxide, + }, sos: { principal: { - name: "Secret of secrets", - slug: "sos", - description: "Secrets of other infisical secrets", - projectId: "1cffa31e-7653-4c0d-9a18-9914a2dbc30b", + name: 'Secret of secrets', + slug: 'sos', + description: 'Secrets of other infisical secrets', + projectId: '1cffa31e-7653-4c0d-9a18-9914a2dbc30b', }, platform: PLATFORMS.Sulfoxide, }, metricsServer: { principal: { - name: "Metrics Server", - slug: "xenon", - description: "Metrics Server for Cluster", - projectId: "", + name: 'Metrics Server', + slug: 'xenon', + description: 'Metrics Server for Cluster', + projectId: '', }, platform: PLATFORMS.Sulfoxide, }, tofu: { principal: { - name: "Tofu", - slug: "tofu", - description: "IaC for AtomiCloud", - projectId: "5c418f54-d211-46dd-a263-e4c07585b47d", + name: 'Tofu', + slug: 'tofu', + description: 'IaC for AtomiCloud', + projectId: '5c418f54-d211-46dd-a263-e4c07585b47d', }, platform: PLATFORMS.Sulfoxide, }, argocd: { principal: { - name: "ArgoCD", - slug: "helium", - description: "Deployment platform using GitOps", - projectId: "7b458fa1-1225-40ce-8e48-3f4a67031bc0", + name: 'ArgoCD', + slug: 'helium', + description: 'Deployment platform using GitOps', + projectId: '7b458fa1-1225-40ce-8e48-3f4a67031bc0', }, platform: PLATFORMS.Sulfoxide, }, backup_engine: { principal: { - name: "Backup Engine", - slug: "fluorine", - description: "Velero as the backup engine for Kubernetes", - projectId: "d712436d-2022-4970-838b-f0b854a83c9c", + name: 'Backup Engine', + slug: 'fluorine', + description: 'Velero as the backup engine for Kubernetes', + projectId: 'd712436d-2022-4970-838b-f0b854a83c9c', }, platform: PLATFORMS.Sulfoxide, }, secrets_engine: { principal: { - name: "Secrets Engine", - slug: "cobalt", - description: "External Secrets to sync secrets from infisical", - projectId: "1c2bc52e-c49b-4307-ad41-ff69a755beed", + name: 'Secrets Engine', + slug: 'cobalt', + description: 'External Secrets to sync secrets from infisical', + projectId: '1c2bc52e-c49b-4307-ad41-ff69a755beed', }, platform: PLATFORMS.Sulfoxide, }, internal_ingress: { principal: { - name: "Internal Ingress", - slug: "boron", - description: "Internal Ingress with cloudflared", - projectId: "9ab0fcd2-ee24-4dd4-8701-c530b888b805", + name: 'Internal Ingress', + slug: 'boron', + description: 'Internal Ingress with cloudflared', + projectId: '9ab0fcd2-ee24-4dd4-8701-c530b888b805', }, platform: PLATFORMS.Sulfoxide, }, external_ingress: { principal: { - name: "External Ingress", - slug: "gold", - description: "External Ingress with nginx", - projectId: "47c29693-3255-4a5b-b0a7-09e0281e1910", + name: 'External Ingress', + slug: 'gold', + description: 'External Ingress with nginx', + projectId: '47c29693-3255-4a5b-b0a7-09e0281e1910', }, platform: PLATFORMS.Sulfoxide, }, scaler: { principal: { - name: "Pod Autoscaler", - slug: "iron", - description: "KEDA scaler for pods", - projectId: "ca687032-485f-4d09-9d7c-5d0f50bf3ab3", + name: 'Pod Autoscaler', + slug: 'iron', + description: 'KEDA scaler for pods', + projectId: 'ca687032-485f-4d09-9d7c-5d0f50bf3ab3', }, platform: PLATFORMS.Sulfoxide, }, cluster_scaler: { principal: { - name: "Cluster Scaler", - slug: "krypton", - description: "Karpenter scaler for nodes", - projectId: "", + name: 'Cluster Scaler', + slug: 'krypton', + description: 'Karpenter scaler for nodes', + projectId: '', }, platform: PLATFORMS.Sulfoxide, }, cluster_issuer: { principal: { - name: "Cluster Issuer", - slug: "zinc", - description: "Cluster Issuer for Certificate", - projectId: "eb3db9f2-3b49-493c-81df-8528121c0ccc", + name: 'Cluster Issuer', + slug: 'zinc', + description: 'Cluster Issuer for Certificate', + projectId: 'eb3db9f2-3b49-493c-81df-8528121c0ccc', }, platform: PLATFORMS.Sulfoxide, }, cert_manager: { principal: { - name: "Cert Manager", - slug: "sulfur", - description: "Certificate Issuing operator", - projectId: "8a244c1b-f58a-40c0-92dd-585ffa2787d1", + name: 'Cert Manager', + slug: 'sulfur', + description: 'Certificate Issuing operator', + projectId: '8a244c1b-f58a-40c0-92dd-585ffa2787d1', }, platform: PLATFORMS.Sulfoxide, }, reloader: { principal: { - name: "Reloader", - slug: "chlorine", - description: "Reloader", - projectId: "", + name: 'Reloader', + slug: 'chlorine', + description: 'Reloader', + projectId: '', }, platform: PLATFORMS.Sulfoxide, }, policy_engine: { principal: { - name: "Policy Engine", - slug: "argon", - description: "Kyverno operator", - projectId: "3cc63883-9ec6-42c1-9772-8570620de422", + name: 'Policy Engine', + slug: 'argon', + description: 'Kyverno operator', + projectId: '3cc63883-9ec6-42c1-9772-8570620de422', }, platform: PLATFORMS.Sulfoxide, }, policies: { principal: { - name: "Policies", - slug: "sodium", - description: "Kyverno policies", - projectId: "cd55ee78-bf70-4853-9bb1-fcc9f3d445cd", + name: 'Policies', + slug: 'sodium', + description: 'Kyverno policies', + projectId: 'cd55ee78-bf70-4853-9bb1-fcc9f3d445cd', }, platform: PLATFORMS.Sulfoxide, }, otel_collector: { principal: { - name: "OpenTelemetry Collector", - slug: "silicon", - description: "OpenTelemetry Collector", - projectId: "3e90c2ef-7007-47a4-bd6a-bac6cdae7396", + name: 'OpenTelemetry Collector', + slug: 'silicon', + description: 'OpenTelemetry Collector', + projectId: '3e90c2ef-7007-47a4-bd6a-bac6cdae7396', }, platform: PLATFORMS.Sulfoxide, }, otel_operator: { principal: { - name: "OpenTelemetry Operator", - slug: "lithium", - description: "OpenTelemetry Operator", - projectId: "ef96dd70-dea3-4548-81b7-0a22231b54ad", + name: 'OpenTelemetry Operator', + slug: 'lithium', + description: 'OpenTelemetry Operator', + projectId: 'ef96dd70-dea3-4548-81b7-0a22231b54ad', }, platform: PLATFORMS.Sulfoxide, }, external_dns: { principal: { - name: "External DNS", - slug: "tin", - description: "External DNS", - projectId: "4c3d88b5-977d-449f-8f4c-2bf9e09e97c8", + name: 'External DNS', + slug: 'tin', + description: 'External DNS', + projectId: '4c3d88b5-977d-449f-8f4c-2bf9e09e97c8', }, platform: PLATFORMS.Sulfoxide, }, vcluster: { principal: { - name: "Virtual Cluster", - slug: "iodine", - description: "Virtual clusters with vcluster", - projectId: "e0c572b3-4eb8-40c6-85ea-f9cf54b6e13b", + name: 'Virtual Cluster', + slug: 'iodine', + description: 'Virtual clusters with vcluster', + projectId: 'e0c572b3-4eb8-40c6-85ea-f9cf54b6e13b', }, platform: PLATFORMS.Sulfoxide, }, @@ -399,37 +408,37 @@ const SERVICE_TREE = { services: { tin: { principal: { - name: "BunnyBooker Polling System", - slug: "tin", - description: "System of pollers for BunnyBooker", - projectId: "df53bb81-dee0-4479-b515-3cab9af7386f", + name: 'BunnyBooker Polling System', + slug: 'tin', + description: 'System of pollers for BunnyBooker', + projectId: 'df53bb81-dee0-4479-b515-3cab9af7386f', }, platform: PLATFORMS.Nitroso, }, zinc: { principal: { - name: "BunnyBooker API Server", - slug: "zinc", - description: "API Server for BunnyBooker", - projectId: "3b4f93e1-aab6-4b4a-a883-55de7eb401ea", + name: 'BunnyBooker API Server', + slug: 'zinc', + description: 'API Server for BunnyBooker', + projectId: '3b4f93e1-aab6-4b4a-a883-55de7eb401ea', }, platform: PLATFORMS.Nitroso, }, argon: { principal: { - name: "BunnyBooker Frontend", - slug: "argon", - description: "Frontend for BunnyBooker", - projectId: "65f937ec-50a5-4551-9338-eb146df712af", + name: 'BunnyBooker Frontend', + slug: 'argon', + description: 'Frontend for BunnyBooker', + projectId: '65f937ec-50a5-4551-9338-eb146df712af', }, platform: PLATFORMS.Nitroso, }, helium: { principal: { - name: "Pollee", - slug: "helium", - description: "Pollee for BunnyBooker", - projectId: "cc897910-0fe7-4784-ac3d-be9847fca2d9", + name: 'Pollee', + slug: 'helium', + description: 'Pollee for BunnyBooker', + projectId: 'cc897910-0fe7-4784-ac3d-be9847fca2d9', }, platform: PLATFORMS.Nitroso, }, @@ -577,22 +586,8 @@ const CLOUD_TREE = { const LANDSCAPE_TREE = { a: [LANDSCAPES.suicune], v: [LANDSCAPES.pichu, LANDSCAPES.pikachu, LANDSCAPES.raichu], - l: [ - LANDSCAPES.lapras, - LANDSCAPES.tauros, - LANDSCAPES.absol, - LANDSCAPES.pinsir, - ], + l: [LANDSCAPES.lapras, LANDSCAPES.tauros, LANDSCAPES.absol, LANDSCAPES.pinsir], p: [LANDSCAPES.entei], } satisfies Record; -export { - LANDSCAPES, - CLOUDS, - CLUSTERS, - CLUSTER_SETS, - PLATFORMS, - SERVICE_TREE, - CLOUD_TREE, - LANDSCAPE_TREE, -}; +export { LANDSCAPES, CLOUDS, CLUSTERS, CLUSTER_SETS, PLATFORMS, SERVICE_TREE, CLOUD_TREE, LANDSCAPE_TREE }; diff --git a/src/lib/utility/git.ts b/src/lib/utility/git.ts index ab06426..3f697ec 100644 --- a/src/lib/utility/git.ts +++ b/src/lib/utility/git.ts @@ -1,4 +1,4 @@ -import { $ } from "bun"; +import { $ } from 'bun'; class Git { async CommitAndPush(dir: string, message: string): Promise { diff --git a/src/lib/utility/http-util.ts b/src/lib/utility/http-util.ts index 99f7323..690ee88 100644 --- a/src/lib/utility/http-util.ts +++ b/src/lib/utility/http-util.ts @@ -1,10 +1,6 @@ -import { $ } from "bun"; +import { $ } from 'bun'; class HttpUtil { - async WaitFor( - interval: number, - url: string, - method: string = "GET", - ): Promise { + async WaitFor(interval: number, url: string, method: string = 'GET'): Promise { while (true) { const response = await fetch(url, { method }); if (response.ok) return; diff --git a/src/lib/utility/kubectl-util.ts b/src/lib/utility/kubectl-util.ts index 72caad6..ff3cac4 100644 --- a/src/lib/utility/kubectl-util.ts +++ b/src/lib/utility/kubectl-util.ts @@ -1,5 +1,5 @@ -import { $ } from "bun"; -import type { UtilPrompter } from "../prompts/util-prompter.ts"; +import { $ } from 'bun'; +import type { UtilPrompter } from '../prompts/util-prompter.ts'; interface ResourceSearch { kind: string; @@ -30,12 +30,10 @@ class KubectlUtil { constructor(private up: UtilPrompter) {} private async generateFlags(search: ResourceSearch): Promise { - const selectorFlag = - (search.selector ?? []).length > 0 ? "--selector=" : ""; - const selector = `${selectorFlag}${(search.selector ?? []).map(([k, v]) => `${k}=${v}`).join(",")}`; - const findFlag = - (search.fieldSelector ?? []).length > 0 ? "--field-selector=" : ""; - const fieldSelector = `${findFlag}${(search.fieldSelector ?? []).map(([k, v]) => `${k}=${v}`).join(",")}`; + const selectorFlag = (search.selector ?? []).length > 0 ? '--selector=' : ''; + const selector = `${selectorFlag}${(search.selector ?? []).map(([k, v]) => `${k}=${v}`).join(',')}`; + const findFlag = (search.fieldSelector ?? []).length > 0 ? '--field-selector=' : ''; + const fieldSelector = `${findFlag}${(search.fieldSelector ?? []).map(([k, v]) => `${k}=${v}`).join(',')}`; return `${selector} ${fieldSelector}`; } @@ -68,7 +66,7 @@ class KubectlUtil { kind: r.kind, context: r.context, namespace: r.namespace, - fieldSelector: [["metadata.name", r.name]], + fieldSelector: [['metadata.name', r.name]], }); const cmds = $.escape( `kubectl --context ${r.context} -n ${r.namespace} wait --for=jsonpath=.status.health.status=Healthy --timeout=6000s ${r.kind} ${r.name}`, @@ -83,16 +81,11 @@ class KubectlUtil { await $`kubectl --context ${r.context} -n ${r.namespace} wait --for=jsonpath=.status.sync.status=Synced --timeout=6000s ${r.kind} ${r.name}`; } - async WaitForApplications( - target: number, - search: ResourceSearch, - ): Promise { + async WaitForApplications(target: number, search: ResourceSearch): Promise { await this.Wait(target, 5, search); const apps = await this.GetRange(search); - const waits = apps.map((x) => this.WaitForApplication(x)); - return Promise.all(waits).then(() => - console.log("✅ All applications are healthy"), - ); + const waits = apps.map(x => this.WaitForApplication(x)); + return Promise.all(waits).then(() => console.log('✅ All applications are healthy')); } async Count(search: ResourceSearch): Promise { @@ -118,12 +111,7 @@ class KubectlUtil { })); } - async Wait( - target: number, - interval: number, - search: ResourceSearch, - intervention?: Intervention, - ): Promise { + async Wait(target: number, interval: number, search: ResourceSearch, intervention?: Intervention): Promise { // number of iterations let count = 0; @@ -131,18 +119,14 @@ class KubectlUtil { let ret = await this.Count(search); // iterate until target is reached while (ret != target) { - console.log( - `🚧 Waiting for all ${search.kind} to reach ${target}, current = ${ret}, index = ${count}...`, - ); + console.log(`🚧 Waiting for all ${search.kind} to reach ${target}, current = ${ret}, index = ${count}...`); await $`sleep ${interval}`; // if intervention is configured if (intervention) { // if intervention threshold is reached if (count % intervention.count === 0 && count != 0) { // check if user wants to exit - const shouldExit = await this.up.YesNo( - "Seems to be taking a long time. Do you want to exit?", - ); + const shouldExit = await this.up.YesNo('Seems to be taking a long time. Do you want to exit?'); if (shouldExit) return true; // provide alternative intervention action if (intervention.action) { @@ -176,55 +160,54 @@ class KubectlUtil { kind: r.kind, context: r.context, namespace: r.namespace, - fieldSelector: [["metadata.name", r.name]], + fieldSelector: [['metadata.name', r.name]], }); } async DeleteNamespace(ns: NamespaceSearch): Promise { const delNS = async () => - $`kubectl delete namespace --context ${ns.context} ${ns.namespace}`.then( - () => console.log("✅ Namespace deleted"), + $`kubectl delete namespace --context ${ns.context} ${ns.namespace}`.then(() => + console.log('✅ Namespace deleted'), ); const delDeployment = async () => - $`kubectl delete deployment --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ Deployments deleted"), + $`kubectl delete deployment --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ Deployments deleted'), ); const delStatefulSet = async () => - $`kubectl delete statefulset --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ StatefulSets deleted"), + $`kubectl delete statefulset --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ StatefulSets deleted'), ); const delJob = async () => - $`kubectl delete job --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ Jobs deleted"), + $`kubectl delete job --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ Jobs deleted'), ); const delDaemonSet = async () => - $`kubectl delete daemonset --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ DaemonSets deleted"), + $`kubectl delete daemonset --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ DaemonSets deleted'), ); const delCronJob = async () => - $`kubectl delete cronjob --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ CronJobs deleted"), + $`kubectl delete cronjob --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ CronJobs deleted'), ); const delPod = async () => - $`kubectl delete pod --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ Pods deleted"), + $`kubectl delete pod --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ Pods deleted'), ); const delReplicaSet = async () => - $`kubectl delete replicaset --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ ReplicaSets deleted"), + $`kubectl delete replicaset --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ ReplicaSets deleted'), ); const delService = async () => - $`kubectl delete service --context ${ns.context} --namespace ${ns.namespace} --all`.then( - () => console.log("✅ Services deleted"), + $`kubectl delete service --context ${ns.context} --namespace ${ns.namespace} --all`.then(() => + console.log('✅ Services deleted'), ); const finalizeNS = async () => { // generate random port: const port = Math.floor(Math.random() * 10000 + 10000); - const timeout = - $`timeout 10 kubectl --context ${ns.context} proxy -p ${port}`.then( - () => console.log("✅ Proxy closed"), - ); + const timeout = $`timeout 10 kubectl --context ${ns.context} proxy -p ${port}`.then(() => + console.log('✅ Proxy closed'), + ); const wait5 = async () => await $`sleep 5`; const finalize = () => wait5() @@ -233,7 +216,7 @@ class KubectlUtil { jq '.spec.finalizers=[]' | \ curl -X PUT http://localhost:${{ raw: port.toString(10) }}/api/v1/namespaces/${{ raw: ns.namespace }}/finalize -H "Content-Type: application/json" --data @-`.nothrow(), ) - .then(() => console.log("💪 Namespace finalizers removal attempted")); + .then(() => console.log('💪 Namespace finalizers removal attempted')); await Promise.all([timeout, finalize()]); }; @@ -244,23 +227,18 @@ class KubectlUtil { while (true) { const ns = await countNS(); console.log(`🔢 NS Count: ${ns.items.length}`); - if (ns.items.length == 0) - return console.log("✅ Namespace finalizers removed"); - console.log( - "😔 Namespace finalizers removal failed, sleep 5 then retry...", - ); + if (ns.items.length == 0) return console.log('✅ Namespace finalizers removed'); + console.log('😔 Namespace finalizers removal failed, sleep 5 then retry...'); await $`sleep 5`; - console.log("💪 Namespace finalizers removal attempted"); + console.log('💪 Namespace finalizers removal attempted'); await finalizeNS(); } }; const count = await countNS(); - if (count.items.length == 0) - return console.log("✅ Namespace already deleted"); + if (count.items.length == 0) return console.log('✅ Namespace already deleted'); - const wait = async () => - await $`sleep 10`.then(() => console.log("✅ Buffer time over")); + const wait = async () => await $`sleep 10`.then(() => console.log('✅ Buffer time over')); const resources: Promise = Promise.all([ delDeployment(), @@ -272,7 +250,7 @@ class KubectlUtil { delReplicaSet(), delService(), ]) - .then(() => console.log("✅ Resources deleted")) + .then(() => console.log('✅ Resources deleted')) .then(wait) .then(finalizeLoop); await Promise.all([delNS(), resources]); diff --git a/src/lib/utility/service-tree-printer.ts b/src/lib/utility/service-tree-printer.ts index 937e3c6..e290968 100644 --- a/src/lib/utility/service-tree-printer.ts +++ b/src/lib/utility/service-tree-printer.ts @@ -1,12 +1,8 @@ -import type { LandscapeCluster } from "../service-tree-def.ts"; +import type { LandscapeCluster } from '../service-tree-def.ts'; class ServiceTreePrinter { - Print( - prefix: string, - [landscape, cluster]: LandscapeCluster, - indent: number = 2, - ): void { - const space = " ".repeat(indent); + Print(prefix: string, [landscape, cluster]: LandscapeCluster, indent: number = 2): void { + const space = ' '.repeat(indent); console.log(`${space}⛰️${prefix} Landscape: \t${landscape.name}`); console.log(`${space}☁️${prefix} Cloud: \t${cluster.cloud.name}`); console.log(`${space}🌀${prefix} Cluster: \t${cluster.principal.name}`); diff --git a/src/lib/utility/yaml-manipulator.ts b/src/lib/utility/yaml-manipulator.ts index dbf6695..dc460b9 100644 --- a/src/lib/utility/yaml-manipulator.ts +++ b/src/lib/utility/yaml-manipulator.ts @@ -1,4 +1,4 @@ -import { parseDocument } from "yaml"; +import { parseDocument } from 'yaml'; class YamlManipulator { async Mutate(path: string, mutations: [string[], unknown][]): Promise { diff --git a/src/tasks/nitroso-waiter.ts b/src/tasks/nitroso-waiter.ts index 23f7b06..c975b9a 100644 --- a/src/tasks/nitroso-waiter.ts +++ b/src/tasks/nitroso-waiter.ts @@ -1,7 +1,7 @@ -import type { Task } from "./tasks.ts"; -import { SERVICE_TREE } from "../lib/service-tree.ts"; -import { KubectlUtil } from "../lib/utility/kubectl-util.ts"; -import { HttpUtil } from "../lib/utility/http-util.ts"; +import type { Task } from './tasks.ts'; +import { SERVICE_TREE } from '../lib/service-tree.ts'; +import { KubectlUtil } from '../lib/utility/kubectl-util.ts'; +import { HttpUtil } from '../lib/utility/http-util.ts'; class NitrosoWaiter { constructor( @@ -9,7 +9,7 @@ class NitrosoWaiter { private h: HttpUtil, ) {} - name: string = "Wait for Nitroso to be ready"; + name: string = 'Wait for Nitroso to be ready'; services: string[] = [ SERVICE_TREE.nitroso.services.tin.principal.slug, SERVICE_TREE.nitroso.services.helium.principal.slug, @@ -32,7 +32,7 @@ class NitrosoWaiter { const name = `${phy}-${cluster}-nitroso-${service}`; console.log(`⏱️ Waiting for ${name} to be ready...`); await this.k.WaitForApplication({ - kind: "app", + kind: 'app', context: adminContext, namespace: adminNamespace, name, @@ -44,16 +44,16 @@ class NitrosoWaiter { const sslName = `api-zinc-nitroso-${v}-tls-x-nitroso-x-${phyLandscape}-${v}-iodine`; const context = `${phyLandscape}-${cluster}`; await this.k.Wait(1, 5, { - kind: "certificate", + kind: 'certificate', context, namespace: v, - fieldSelector: [["metadata.name", sslName]], + fieldSelector: [['metadata.name', sslName]], }); await this.k.Wait(1, 5, { - kind: "secret", + kind: 'secret', context, namespace: v, - fieldSelector: [["metadata.name", sslName]], + fieldSelector: [['metadata.name', sslName]], }); } // wait for endpoint to be ready diff --git a/src/tasks/sulfoxide-boron-waiter.ts b/src/tasks/sulfoxide-boron-waiter.ts index a3422c4..8751a31 100644 --- a/src/tasks/sulfoxide-boron-waiter.ts +++ b/src/tasks/sulfoxide-boron-waiter.ts @@ -1,6 +1,6 @@ -import type { Task } from "./tasks.ts"; -import type { KubectlUtil } from "../lib/utility/kubectl-util.ts"; -import type { ServiceTreeService } from "../lib/service-tree-def.ts"; +import type { Task } from './tasks.ts'; +import type { KubectlUtil } from '../lib/utility/kubectl-util.ts'; +import type { ServiceTreeService } from '../lib/service-tree-def.ts'; class SulfoxideBoronWaiter { constructor( @@ -8,21 +8,21 @@ class SulfoxideBoronWaiter { private sulfoxideBoron: ServiceTreeService, ) {} - name: string = "Wait for Sulfoxide Helium to be ready"; + name: string = 'Wait for Sulfoxide Helium to be ready'; task(context: string, namespace: string): Task { return [ this.name, async () => { const boron = this.sulfoxideBoron; - console.log("⏱️ Waiting for Boron to be ready..."); + console.log('⏱️ Waiting for Boron to be ready...'); await this.k.WaitForReplica({ - kind: "deployment", + kind: 'deployment', context, namespace, name: `${boron.platform.slug}-${boron.principal.slug}`, }); - console.log("✅ Boron is ready"); + console.log('✅ Boron is ready'); }, ]; } diff --git a/src/tasks/sulfoxide-helium-waiter.ts b/src/tasks/sulfoxide-helium-waiter.ts index 343a5f5..6a7c73c 100644 --- a/src/tasks/sulfoxide-helium-waiter.ts +++ b/src/tasks/sulfoxide-helium-waiter.ts @@ -1,6 +1,6 @@ -import type { Task } from "./tasks.ts"; -import type { KubectlUtil } from "../lib/utility/kubectl-util.ts"; -import type { ServiceTreeService } from "../lib/service-tree-def.ts"; +import type { Task } from './tasks.ts'; +import type { KubectlUtil } from '../lib/utility/kubectl-util.ts'; +import type { ServiceTreeService } from '../lib/service-tree-def.ts'; class SulfoxideHeliumWaiter { constructor( @@ -8,7 +8,7 @@ class SulfoxideHeliumWaiter { private sulfoxideHelium: ServiceTreeService, ) {} - name: string = "Wait for Sulfoxide Helium to be ready"; + name: string = 'Wait for Sulfoxide Helium to be ready'; task(context: string, namespace: string): Task { return [ @@ -26,7 +26,7 @@ class SulfoxideHeliumWaiter { for (const name of deployments) { console.log(`🚧 Waiting for ${name} to be ready...`); await this.k.WaitForReplica({ - kind: "deployment", + kind: 'deployment', context, namespace, name, @@ -35,7 +35,7 @@ class SulfoxideHeliumWaiter { } console.log(`🚧 Waiting for statefulset to be ready...`); await this.k.WaitForReplica({ - kind: "statefulset", + kind: 'statefulset', context, namespace, name: `${prefix}-application-controller`, diff --git a/src/tasks/sulfoxide-xenon-waiter.ts b/src/tasks/sulfoxide-xenon-waiter.ts index bba1732..0c8ed43 100644 --- a/src/tasks/sulfoxide-xenon-waiter.ts +++ b/src/tasks/sulfoxide-xenon-waiter.ts @@ -1,6 +1,6 @@ -import type { Task } from "./tasks.ts"; -import type { KubectlUtil } from "../lib/utility/kubectl-util.ts"; -import type { ServiceTreeService } from "../lib/service-tree-def.ts"; +import type { Task } from './tasks.ts'; +import type { KubectlUtil } from '../lib/utility/kubectl-util.ts'; +import type { ServiceTreeService } from '../lib/service-tree-def.ts'; class SulfoxideXenonWaiter { constructor( @@ -8,7 +8,7 @@ class SulfoxideXenonWaiter { private sulfoxideXenon: ServiceTreeService, ) {} - name: string = "Wait for Sulfoxide Xenon to be ready"; + name: string = 'Wait for Sulfoxide Xenon to be ready'; task(context: string, namespace: string): Task { return [ @@ -18,7 +18,7 @@ class SulfoxideXenonWaiter { const name = `${Xe.platform.slug}-${Xe.principal.slug}-metrics-server`; console.log(`🚧 Waiting for deployment ${name} to be ready...`); await this.k.WaitForReplica({ - kind: "deployment", + kind: 'deployment', context, namespace, name, diff --git a/src/tasks/tasks.ts b/src/tasks/tasks.ts index b1c6d93..d385f8b 100644 --- a/src/tasks/tasks.ts +++ b/src/tasks/tasks.ts @@ -1,5 +1,5 @@ -import pc from "picocolors"; -import type { UtilPrompter } from "../lib/prompts/util-prompter.ts"; +import pc from 'picocolors'; +import type { UtilPrompter } from '../lib/prompts/util-prompter.ts'; // name, action interface ComplexTask { @@ -63,10 +63,8 @@ class TaskRunner { * @constructor */ async Run(task: Task): Promise { - const r = await this.up.YesNoExit( - `Do you want to run task '${this.extractName(task)}'?`, - ); - if (r === "exit") process.exit(0); + const r = await this.up.YesNoExit(`Do you want to run task '${this.extractName(task)}'?`); + if (r === 'exit') process.exit(0); if (r) await this.Exec(task); } }