@fairmint/canton-node-sdk
Version:
Canton Node SDK
144 lines (125 loc) • 4.61 kB
text/typescript
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
import * as cloudsql from '@google-cloud/sql';
import * as gcp from '@pulumi/gcp';
import * as automation from '@pulumi/pulumi/automation';
import * as readline from 'readline';
import { program } from 'commander';
import { CLUSTER_BASENAME, config } from '@lfdecentralizedtrust/splice-pulumi-common';
import { stack } from './pulumi';
import { runSvCantonForAllMigrations } from './sv-canton/pulumi';
const gcpSqlClient = new cloudsql.SqlInstancesServiceClient({
fallback: 'rest',
});
async function getDBsInStack(stack: automation.Stack): Promise<gcp.sql.DatabaseInstance[]> {
const exported = await stack.exportStack();
const resources = exported.deployment.resources;
if (!resources) {
return Promise.resolve([]);
}
const res = resources.filter((r: any) => r.type === 'gcp:sql/databaseInstance:DatabaseInstance');
console.log(
`In ${stack.name} got ${JSON.stringify(
res.map((x: any) => x.id),
null,
2
)}`
);
return res;
}
async function getAllPulumiDbs(): Promise<gcp.sql.DatabaseInstance[]> {
const projects = ['canton-network', 'sv-runbook', 'splitwell', 'validator1'];
const coreDbs = await Promise.all(
projects.map(async project => await getDBsInStack(await stack(project, project, true, {})))
).then(dbs => dbs.flat());
const readDbsForAllStacks = runSvCantonForAllMigrations(
'get_dbs',
async stack => {
return getDBsInStack(stack);
},
false,
true
);
const migrationDbsRet = await Promise.all(readDbsForAllStacks.map(res => res.promise)).then(
result => Array.from(result.values()).flat()
);
return [...coreDbs, ...migrationDbsRet];
}
async function getAllGcpDbs(): Promise<cloudsql.protos.google.cloud.sql.v1.IDatabaseInstance[]> {
const gcp_project = config.requireEnv('CLOUDSDK_CORE_PROJECT');
const filter = `settings.userLabels.cluster:${CLUSTER_BASENAME}`;
// console.log(filter);
const request = {
project: gcp_project,
filter: filter,
};
const result = await gcpSqlClient.list(request);
return result[0].items ?? [];
}
function prettyPrintDb(db: cloudsql.protos.google.cloud.sql.v1.IDatabaseInstance) {
const createTimeSeconds = db.createTime?.seconds;
const createTime = createTimeSeconds
? new Date((createTimeSeconds as number) * 1000).toDateString()
: 'unknown';
const size = `${db.settings?.dataDiskSizeGb?.value ?? 'unknown'} GB`;
console.log(`* Database: ${db.name} (State: ${db.state}, created: ${createTime}, size: ${size})`);
}
async function deleteDb(db: cloudsql.protos.google.cloud.sql.v1.IDatabaseInstance) {
const request = {
instance: db.name,
project: db.project,
};
console.log(`Deleting ${db.name}...`);
await gcpSqlClient.delete(request);
console.log(`Done deleting ${db.name}`);
}
async function runPurgeUnusedDbs() {
program.option('-y, --yes', 'Auto-accept all prompts').parse(process.argv);
const options = program.opts();
const autoAccept = options.yes;
const usedDbs = await getAllPulumiDbs();
// DatabaseInstance.id is a string, but ts insists on it being an Output<string>, so we force-cast it via an unknown cast
const usedDbNames: string[] = usedDbs.map(
(db: gcp.sql.DatabaseInstance) => db.id
) as unknown as string[];
const allDbs = await getAllGcpDbs();
const unusedDbs = allDbs.filter(db => !usedDbNames.some(usedDb => usedDb == db.name));
if (unusedDbs.length != allDbs.length - usedDbNames.length) {
console.warn('Warning: There are some databases in Pulumi that were not found in GCP');
}
if (unusedDbs.length == 0) {
console.log('No unused databases found');
return;
}
console.log(`About to delete the following ${unusedDbs.length} database instances:`);
unusedDbs.forEach(db => prettyPrintDb(db));
if (autoAccept) {
console.log('Auto-accepting');
for (const db of unusedDbs) {
await deleteDb(db);
}
} else {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(
'\nDo you want to proceed with deleting these DB instances? [y/n] ',
async answer => {
if (answer === 'y') {
console.log('Deleting databases');
for (const db of unusedDbs) {
await deleteDb(db);
}
} else {
console.log('Aborting');
}
rl.close();
}
);
}
}
runPurgeUnusedDbs().catch(e => {
console.error(e);
process.exit(1);
});