Created
December 25, 2019 08:17
-
-
Save arielshaqed/8e22d59bbb07feb30fe6cd11480477a1 to your computer and use it in GitHub Desktop.
Recursively clean up old projects (*including* their clusters) from MongoDB Atlas: anything starting `autoAtlas-`.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as baseRequest from 'request-promise-native'; | |
import { StatusCodeError } from 'request-promise-native/errors'; | |
import { concurrently, backoff } from '@binaris/nodeutils'; | |
const orgId = process.env.ATLAS_ORG_ID | |
const publicKey = process.env.ATLAS_PUBLIC_KEY; | |
const privateKey = process.env.ATLAS_PRIVATE_KEY; | |
const concurrency = parseInt(process.env.ATLAS_CONCURRENCY || '30', 10); | |
const request = baseRequest.defaults({ | |
auth: { user: publicKey, password: privateKey, sendImmediately: false }, | |
json: true, | |
baseUrl: 'https://cloud.mongodb.com/api/atlas/v1.0/', | |
simple: true, | |
}); | |
async function getProjectIds(): Promise<string[]> { | |
const res = await request.get(`orgs/${orgId}/groups`); | |
return res.results.map(({ id }: any) => id as string); | |
} | |
interface Project { | |
clusterCount: number; | |
created: string; | |
id: string; | |
name: string; | |
// Other fields exist notably links. See | |
// https://docs.atlas.mongodb.com/reference/api/project-get-one/ | |
} | |
async function getProject(projectId: string): Promise<Project> { | |
const res = await request.get(`groups/${projectId}`); | |
return res; | |
} | |
async function getProjects() { | |
const projectIds = await getProjectIds(); | |
return await concurrently(projectIds.map((projectId: string) => (() => getProject(projectId))), concurrency); | |
} | |
async function getClusterNamesInProject(projectId: string): Promise<string[]> { | |
const res = await request.get(`groups/${projectId}/clusters`); | |
return res.results.map(({ name }: any) => name); | |
} | |
async function clusterExists(projectId: string, clusterName: string): Promise<boolean> { | |
try { | |
await request.get(`groups/${projectId}/clusters/${clusterName}`); | |
return true; | |
} catch (e) { | |
if (!(e instanceof StatusCodeError)) throw e; | |
if (e.statusCode !== 404) throw e; | |
return false; | |
} | |
} | |
async function deleteCluster(projectId: string, clusterName: string) { | |
console.log('DELETE project', projectId, 'cluster', clusterName); | |
return request.delete(`groups/${projectId}/clusters/${clusterName}`); | |
} | |
async function deleteProject(projectId: string) { | |
return request.delete(`groups/${projectId}`); | |
} | |
const errors: any[] = []; | |
// When deleting *many* projects, pass concurrency === 1. | |
async function deleteProjectContents(projectId: string, concurrency = 1) { | |
async function wait(clusterName: string) { | |
console.log(`Waiting for ${clusterName}...`); | |
try { | |
await backoff.retry( | |
async () => { | |
if (await clusterExists(projectId, clusterName)) throw new Error('please continue'); | |
return true; | |
}, | |
{ initialMsec: 500, factor: 1.01, lowNoise: 0.7, highNoise: 0.3, maxMsec: 60000}); | |
console.log(`${clusterName} deleted!`); | |
} catch (e) { | |
console.log(`giving up on ${clusterName}`); | |
} | |
} | |
const clusterNames = await getClusterNamesInProject(projectId); | |
await concurrently( | |
clusterNames.map((clusterName) => async () => { | |
try { | |
await deleteCluster(projectId, clusterName); | |
await wait(clusterName); | |
} catch (e) { | |
if (! (e instanceof StatusCodeError)) throw e; | |
if (e.error.errorCode !== 'CLUSTER_NOT_FOUND' && | |
e.error.errorCode !== 'CLUSTER_ALREADY_REQUESTED_DELETION') { | |
throw e; | |
} | |
console.error(e.error.detail); | |
errors.push(e.error); | |
await wait(clusterName); | |
} | |
}), | |
concurrency); | |
deleteProject(projectId).catch((_) => undefined /* ignore */); | |
} | |
async function main() { | |
const projectIds = (await getProjects()) | |
.filter(({ name }) => name.startsWith('autoAtlas-')) | |
.map(({ id }) => id); | |
console.log(`Deleting ${projectIds.length} projects...`); | |
await concurrently( | |
projectIds.map((projectId) => () => deleteProjectContents(projectId, 1)), | |
concurrency | |
); | |
} | |
main().then(() => { | |
if (errors.length) { | |
console.error(`${errors.length} failed`); | |
process.exit(1); | |
} | |
console.log('Done!'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment