diff --git a/functions/src/api/reportBuildFailure.ts b/functions/src/api/reportBuildFailure.ts index 379e914..301535c 100644 --- a/functions/src/api/reportBuildFailure.ts +++ b/functions/src/api/reportBuildFailure.ts @@ -13,8 +13,7 @@ const internalToken = defineSecret('INTERNAL_TOKEN'); export const reportBuildFailure = onRequest( { secrets: [discordToken, internalToken] }, async (req: Request, res: Response) => { - const discordClient = new Discord(); - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); try { if (!Token.isValid(req.header('authorization'), internalToken.value())) { @@ -41,7 +40,7 @@ export const reportBuildFailure = onRequest( `; logger.error(message, err); - await discordClient.sendAlert(message); + await Discord.sendAlert(message); if (req.body?.jobId?.toString().startsWith('dryRun')) { await CiBuilds.removeDryRunBuild(req.body.buildId); @@ -51,6 +50,6 @@ export const reportBuildFailure = onRequest( res.status(500).send('Something went wrong'); } - await discordClient.disconnect(); + Discord.disconnect(); }, ); diff --git a/functions/src/api/reportNewBuild.ts b/functions/src/api/reportNewBuild.ts index da6481d..ad27e55 100644 --- a/functions/src/api/reportNewBuild.ts +++ b/functions/src/api/reportNewBuild.ts @@ -16,8 +16,7 @@ const internalToken = defineSecret('INTERNAL_TOKEN'); export const reportNewBuild = onRequest( { secrets: [discordToken, internalToken] }, async (req: Request, res: Response) => { - const discordClient = new Discord(); - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); try { if (!Token.isValid(req.header('authorization'), internalToken.value())) { @@ -53,7 +52,7 @@ export const reportNewBuild = onRequest( ${err.message} `; logger.error(message, err); - await discordClient.sendAlert(message); + await Discord.sendAlert(message); if (req.body?.jobId?.toString().startsWith('dryRun')) { await CiBuilds.removeDryRunBuild(req.body.buildId); @@ -61,6 +60,8 @@ export const reportNewBuild = onRequest( } res.status(500).send('Something went wrong'); + + Discord.disconnect(); } }, ); diff --git a/functions/src/api/reportPublication.ts b/functions/src/api/reportPublication.ts index 063fb3a..10be97e 100644 --- a/functions/src/api/reportPublication.ts +++ b/functions/src/api/reportPublication.ts @@ -14,8 +14,7 @@ const internalToken = defineSecret('INTERNAL_TOKEN'); export const reportPublication = onRequest( { secrets: [discordToken, internalToken] }, async (req: Request, res: Response) => { - const discordClient = new Discord(); - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); try { if (!Token.isValid(req.header('authorization'), internalToken.value())) { @@ -50,7 +49,7 @@ export const reportPublication = onRequest( } logger.info(message); if (!isDryRun) { - await discordClient.sendNews(message); + await Discord.sendNews(message); } } @@ -67,7 +66,7 @@ export const reportPublication = onRequest( ${err.message} `; logger.error(message, err); - await discordClient.sendAlert(message); + await Discord.sendAlert(message); if (req.body?.jobId?.toString().startsWith('dryRun')) { await CiBuilds.removeDryRunBuild(req.body.buildId); @@ -77,6 +76,6 @@ export const reportPublication = onRequest( res.status(500).send('Something went wrong'); } - await discordClient.disconnect(); + await Discord.disconnect(); }, ); diff --git a/functions/src/api/retryBuild.ts b/functions/src/api/retryBuild.ts index 32c170f..0476b63 100644 --- a/functions/src/api/retryBuild.ts +++ b/functions/src/api/retryBuild.ts @@ -16,8 +16,7 @@ const githubClientSecret = defineSecret('GITHUB_CLIENT_SECRET'); export const retryBuild = onRequest( { secrets: [discordToken, githubClientSecret, githubPrivateKey] }, async (request: Request, response: Response) => { - const discordClient = new Discord(); - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); try { response.set('Content-Type', 'application/json'); @@ -82,13 +81,7 @@ export const retryBuild = onRequest( const gitHubClient = await GitHub.init(githubPrivateKey.value(), githubClientSecret.value()); const repoVersionInfo = await RepoVersionInfo.getLatest(); const scheduler = new Ingeminator(1, gitHubClient, repoVersionInfo); - const scheduledSuccessfully = await scheduler.rescheduleBuild( - jobId, - job, - buildId, - build, - discordClient, - ); + const scheduledSuccessfully = await scheduler.rescheduleBuild(jobId, job, buildId, build); // Report result if (scheduledSuccessfully) { @@ -108,5 +101,7 @@ export const retryBuild = onRequest( console.log('error', JSON.stringify(error, Object.getOwnPropertyNames(error))); response.status(401).send({ message: 'Unauthorized' }); } + + Discord.disconnect(); }, ); diff --git a/functions/src/api/testFunction.ts b/functions/src/api/testFunction.ts index 896865b..d352f3a 100644 --- a/functions/src/api/testFunction.ts +++ b/functions/src/api/testFunction.ts @@ -23,12 +23,11 @@ export const testFunction = onRequest( }, async (request: Request, response: Response) => { // Run all non-sensitive functions to verify that the deployment is working. - const discordClient = new Discord(); let info = 'Ok'; let code = 200; try { - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); const versions = await scrapeVersions( githubPrivateKeyConfigSecret.value(), @@ -53,7 +52,7 @@ export const testFunction = onRequest( info = error.message; code = 500; } finally { - await discordClient.disconnect(); + await Discord.disconnect(); } response.status(code).send(info); diff --git a/functions/src/cron/index.ts b/functions/src/cron/index.ts index f470649..526e402 100644 --- a/functions/src/cron/index.ts +++ b/functions/src/cron/index.ts @@ -26,12 +26,10 @@ export const trigger = onSchedule( secrets: [discordToken, githubPrivateKeyConfigSecret, githubClientSecretConfigSecret], }, async () => { - const discordClient = new Discord(); - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); try { await routineTasks( - discordClient, githubPrivateKeyConfigSecret.value(), githubClientSecretConfigSecret.value(), ); @@ -43,29 +41,25 @@ export const trigger = onSchedule( const routineTasksFailedMessage = `Something went wrong while running routine tasks.\n${fullError}`; logger.error(routineTasksFailedMessage); - await discordClient.sendAlert(routineTasksFailedMessage); + await Discord.sendAlert(routineTasksFailedMessage); } - await discordClient.disconnect(); + Discord.disconnect(); }, ); -const routineTasks = async ( - discordClient: Discord, - githubPrivateKey: string, - githubClientSecret: string, -) => { +const routineTasks = async (githubPrivateKey: string, githubClientSecret: string) => { try { - await discordClient.sendDebugLine('begin'); + await Discord.sendDebugLine('begin'); await dataMigrations(); - await ingestRepoVersions(discordClient, githubPrivateKey, githubClientSecret); - await ingestUnityVersions(discordClient); - await cleanUpBuilds(discordClient); - await scheduleBuildsFromTheQueue(discordClient, githubPrivateKey, githubClientSecret); + await ingestRepoVersions(githubPrivateKey, githubClientSecret); + await ingestUnityVersions(); + await cleanUpBuilds(); + await scheduleBuildsFromTheQueue(githubPrivateKey, githubClientSecret); } catch (error: any) { logger.error(error); - await discordClient.sendAlert(error); + await Discord.sendAlert(error); } finally { - await discordClient.sendDebugLine('end'); + await Discord.sendDebugLine('end'); } }; diff --git a/functions/src/logic/buildQueue/cleanUpBuilds.ts b/functions/src/logic/buildQueue/cleanUpBuilds.ts index a6baf78..4bb585c 100644 --- a/functions/src/logic/buildQueue/cleanUpBuilds.ts +++ b/functions/src/logic/buildQueue/cleanUpBuilds.ts @@ -1,6 +1,5 @@ -import { Discord } from '../../service/discord'; import { Cleaner } from './cleaner'; -export const cleanUpBuilds = async (discordClient: Discord) => { - await Cleaner.cleanUp(discordClient); +export const cleanUpBuilds = async () => { + await Cleaner.cleanUp(); }; diff --git a/functions/src/logic/buildQueue/cleaner.ts b/functions/src/logic/buildQueue/cleaner.ts index a4791c1..0259d54 100644 --- a/functions/src/logic/buildQueue/cleaner.ts +++ b/functions/src/logic/buildQueue/cleaner.ts @@ -8,12 +8,12 @@ export class Cleaner { static buildsProcessed: number; - public static async cleanUp(discordClient: Discord) { + public static async cleanUp() { this.buildsProcessed = 0; - await this.cleanUpBuildsThatDidntReportBack(discordClient); + await this.cleanUpBuildsThatDidntReportBack(); } - private static async cleanUpBuildsThatDidntReportBack(discordClient: Discord) { + private static async cleanUpBuildsThatDidntReportBack() { const startedBuilds = await CiBuilds.getStartedBuilds(); for (const startedBuild of startedBuilds) { @@ -27,7 +27,7 @@ export class Cleaner { if (publishedDate) { const buildWasPublishedAlreadyMessage = `[Cleaner] Build "${tag}" has a publication date, but it's status is "started". Was a rebuild requested for this build?`; - await discordClient.sendDebug(buildWasPublishedAlreadyMessage); + await Discord.sendDebug(buildWasPublishedAlreadyMessage); // Maybe set status to published. However, that will increase complexity. // Deleting a tag from dockerhub and rebuilding will yield this error currently. @@ -38,7 +38,7 @@ export class Cleaner { if (!lastBuildStart) { // In theory this should never happen. - await discordClient.sendAlert( + await Discord.sendAlert( `[Cleaner] Build "${tag}" with status "started" does not have a "lastBuildStart" date.`, ); continue; @@ -59,7 +59,7 @@ export class Cleaner { // Image does not exist if (!response) { const markAsFailedMessage = `[Cleaner] Build for "${tag}" with status "started" never reported back in. Marking it as failed. It will retry automatically.`; - await discordClient.sendAlert(markAsFailedMessage); + await Discord.sendAlert(markAsFailedMessage); await CiBuilds.markBuildAsFailed(buildId, { reason: markAsFailedMessage, }); @@ -69,7 +69,7 @@ export class Cleaner { // Image exists const markAsSuccessfulMessage = `[Cleaner] Build for "${tag}" got stuck. But the image was successfully uploaded. Marking it as published.`; - await discordClient.sendDebug(markAsSuccessfulMessage); + await Discord.sendDebug(markAsSuccessfulMessage); await CiBuilds.markBuildAsPublished(buildId, jobId, { digest: '', // missing from dockerhub v1 api payload specificTag: `${baseOs}-${repoVersion}`, diff --git a/functions/src/logic/buildQueue/ingeminator.ts b/functions/src/logic/buildQueue/ingeminator.ts index c2d4443..23c0d15 100644 --- a/functions/src/logic/buildQueue/ingeminator.ts +++ b/functions/src/logic/buildQueue/ingeminator.ts @@ -23,7 +23,7 @@ export class Ingeminator { this.repoVersionInfo = repoVersionInfo; } - async rescheduleFailedJobs(jobs: CiJobQueue, discordClient: Discord) { + async rescheduleFailedJobs(jobs: CiJobQueue) { if (jobs.length <= 0) { throw new Error( '[Ingeminator] Expected ingeminator to be called with jobs to retry, none were given.', @@ -37,15 +37,15 @@ export class Ingeminator { ); } - await this.rescheduleFailedBuildsForJob(job, discordClient); + await this.rescheduleFailedBuildsForJob(job); } } - private async rescheduleFailedBuildsForJob(job: CiJobQueueItem, discordClient: Discord) { + private async rescheduleFailedBuildsForJob(job: CiJobQueueItem) { const { id: jobId, data: jobData } = job; const builds = await CiBuilds.getFailedBuildsQueue(jobId); if (builds.length <= 0) { - await discordClient.sendDebug( + await Discord.sendDebug( `[Ingeminator] Looks like all failed builds for job \`${jobId}\` are already scheduled.`, ); return; @@ -56,7 +56,7 @@ export class Ingeminator { // Space for more? if (this.numberToSchedule <= 0) { - await discordClient.sendDebug( + await Discord.sendDebug( `[Ingeminator] waiting for more spots to become available for builds of ${jobId}.`, ); return; @@ -78,9 +78,9 @@ export class Ingeminator { const alertingPeriodMilliseconds = alertingPeriodMinutes * 60 * 1000; if (lastFailure.toMillis() + alertingPeriodMilliseconds >= Timestamp.now().toMillis()) { logger.error(maxRetriesReachedMessage); - await discordClient.sendAlert(maxRetriesReachedMessage); + await Discord.sendAlert(maxRetriesReachedMessage); } else { - await discordClient.sendDebug(maxRetriesReachedMessage); + await Discord.sendDebug(maxRetriesReachedMessage); } return; @@ -90,7 +90,7 @@ export class Ingeminator { const backoffMinutes = failureCount * 15; const backoffMilliseconds = backoffMinutes * 60 * 1000; if (lastFailure.toMillis() + backoffMilliseconds >= Timestamp.now().toMillis()) { - await discordClient.sendDebug( + await Discord.sendDebug( `[Ingeminator] Backoff period of ${backoffMinutes} minutes has not expired for ${buildId}.`, ); continue; @@ -98,24 +98,16 @@ export class Ingeminator { // Schedule a build this.numberToSchedule -= 1; - if (!(await this.rescheduleBuild(jobId, jobData, buildId, BuildData, discordClient))) { + if (!(await this.rescheduleBuild(jobId, jobData, buildId, BuildData))) { return; } } await CiJobs.markJobAsScheduled(jobId); - await discordClient.sendDebug( - `[Ingeminator] rescheduled any failing editor images for ${jobId}.`, - ); + await Discord.sendDebug(`[Ingeminator] rescheduled any failing editor images for ${jobId}.`); } - public async rescheduleBuild( - jobId: string, - jobData: CiJob, - buildId: string, - buildData: CiBuild, - discordClient: Discord, - ) { + public async rescheduleBuild(jobId: string, jobData: CiJob, buildId: string, buildData: CiBuild) { // Info from job const { editorVersionInfo } = jobData; const { version: editorVersion, changeSet } = editorVersionInfo as EditorVersionInfo; @@ -151,7 +143,7 @@ export class Ingeminator { [Ingeminator] failed to ingeminate job ${jobId}, status: ${response.status}, response: ${response.data}.`; logger.error(failureMessage); - await discordClient.sendAlert(failureMessage); + await Discord.sendAlert(failureMessage); return false; } diff --git a/functions/src/logic/buildQueue/scheduleBuildsFromTheQueue.ts b/functions/src/logic/buildQueue/scheduleBuildsFromTheQueue.ts index 3ae5f13..5e3b867 100644 --- a/functions/src/logic/buildQueue/scheduleBuildsFromTheQueue.ts +++ b/functions/src/logic/buildQueue/scheduleBuildsFromTheQueue.ts @@ -15,7 +15,6 @@ import { Discord } from '../../service/discord'; * This schedule is based on that knowledge and assumption */ export const scheduleBuildsFromTheQueue = async ( - discordClient: Discord, githubPrivateKey: string, githubClientSecret: string, ) => { @@ -24,29 +23,29 @@ export const scheduleBuildsFromTheQueue = async ( const testVersion = '0.1.0'; if (repoVersionInfo.version === testVersion) { - await discordClient.sendDebug('[Build queue] No longer building test versions.'); + await Discord.sendDebug('[Build queue] No longer building test versions.'); return; } - if (!(await scheduler.ensureThatBaseImageHasBeenBuilt(discordClient))) { - await discordClient.sendDebug('[Build queue] Waiting for base image to be ready.'); + if (!(await scheduler.ensureThatBaseImageHasBeenBuilt())) { + await Discord.sendDebug('[Build queue] Waiting for base image to be ready.'); return; } - if (!(await scheduler.ensureThatHubImageHasBeenBuilt(discordClient))) { - await discordClient.sendDebug('[Build queue] Waiting for hub image to be ready.'); + if (!(await scheduler.ensureThatHubImageHasBeenBuilt())) { + await Discord.sendDebug('[Build queue] Waiting for hub image to be ready.'); return; } - if (!(await scheduler.ensureThereAreNoFailedJobs(discordClient))) { - await discordClient.sendDebug('[Build queue] Retrying failed jobs before scheduling new jobs.'); + if (!(await scheduler.ensureThereAreNoFailedJobs())) { + await Discord.sendDebug('[Build queue] Retrying failed jobs before scheduling new jobs.'); return; } - if (!(await scheduler.buildLatestEditorImages(discordClient))) { - await discordClient.sendDebug('[Build queue] Editor images are building.'); + if (!(await scheduler.buildLatestEditorImages())) { + await Discord.sendDebug('[Build queue] Editor images are building.'); return; } - await discordClient.sendDebug('[Build queue] Idle 🎈'); + await Discord.sendDebug('[Build queue] Idle 🎈'); }; diff --git a/functions/src/logic/buildQueue/scheduler.ts b/functions/src/logic/buildQueue/scheduler.ts index 36b212e..6b254c1 100644 --- a/functions/src/logic/buildQueue/scheduler.ts +++ b/functions/src/logic/buildQueue/scheduler.ts @@ -75,7 +75,7 @@ export class Scheduler { return this; } - async ensureThatBaseImageHasBeenBuilt(discordClient: Discord): Promise { + async ensureThatBaseImageHasBeenBuilt(): Promise { // Get base image information const jobId = CiJobs.parseJobId(Image.types.base, this.repoVersion); const job = await CiJobs.get(jobId); @@ -103,12 +103,12 @@ export class Scheduler { [Scheduler] failed to schedule job ${jobId}, status: ${response.status}, response: ${response.data}`; logger.error(failureMessage); - await discordClient.sendAlert(failureMessage); + await Discord.sendAlert(failureMessage); return false; } await CiJobs.markJobAsScheduled(jobId); - await discordClient.sendDebug(`[Scheduler] Scheduled new base image build (${jobId}).`); + await Discord.sendDebug(`[Scheduler] Scheduled new base image build (${jobId}).`); return false; } @@ -116,7 +116,7 @@ export class Scheduler { return job.status === 'completed'; } - async ensureThatHubImageHasBeenBuilt(discordClient: Discord): Promise { + async ensureThatHubImageHasBeenBuilt(): Promise { // Get hub image information const jobId = CiJobs.parseJobId(Image.types.hub, this.repoVersion); const job = await CiJobs.get(jobId); @@ -144,12 +144,12 @@ export class Scheduler { [Scheduler] failed to schedule job ${jobId}, status: ${response.status}, response: ${response.data}`; logger.error(failureMessage); - await discordClient.sendAlert(failureMessage); + await Discord.sendAlert(failureMessage); return false; } await CiJobs.markJobAsScheduled(jobId); - await discordClient.sendDebug(`[Scheduler] Scheduled new hub image build (${jobId})`); + await Discord.sendDebug(`[Scheduler] Scheduled new hub image build (${jobId})`); return false; } @@ -163,7 +163,7 @@ export class Scheduler { * CiJobs will stay "failed" until all builds complete. * This will prevent creating failures on 1000+ builds */ - async ensureThereAreNoFailedJobs(discordClient: Discord): Promise { + async ensureThereAreNoFailedJobs(): Promise { const { maxToleratedFailures, maxExtraJobsForRescheduling } = settings; const failingJobs = await CiJobs.getFailingJobsQueue(); @@ -172,25 +172,21 @@ export class Scheduler { const numberToReschedule = openSpots + maxExtraJobsForRescheduling; if (numberToReschedule <= 0) { - await discordClient.sendDebug( - '[Scheduler] Not retrying any new jobs, as the queue is full', - ); + await Discord.sendDebug('[Scheduler] Not retrying any new jobs, as the queue is full'); return false; } const ingeminator = new Ingeminator(numberToReschedule, this.gitHub, this.repoVersionInfo); - await ingeminator.rescheduleFailedJobs(failingJobs, discordClient); + await ingeminator.rescheduleFailedJobs(failingJobs); } return failingJobs.length <= maxToleratedFailures; } - async buildLatestEditorImages(discordClient: Discord): Promise { + async buildLatestEditorImages(): Promise { const openSpots = await this.determineOpenSpots(); if (openSpots <= 0) { - await discordClient.sendDebug( - '[Scheduler] Not scheduling any new jobs, as the queue is full', - ); + await Discord.sendDebug('[Scheduler] Not scheduling any new jobs, as the queue is full'); return false; } @@ -206,7 +202,7 @@ export class Scheduler { // Schedule CiJobs as workflows, which will report back CiBuilds. const toBeScheduledJobs = take(queue, openSpots); const jobsAsString = toBeScheduledJobs?.map((job) => job.id).join(',\n'); - await discordClient.sendDebug(`[Scheduler] top of the queue: \n ${jobsAsString}`); + await Discord.sendDebug(`[Scheduler] top of the queue: \n ${jobsAsString}`); for (const toBeScheduledJob of toBeScheduledJobs) { const { id: jobId, data } = toBeScheduledJob; @@ -233,12 +229,12 @@ export class Scheduler { [Scheduler] failed to schedule job ${jobId}, status: ${response.status}, response: ${response.data}`; logger.error(failureMessage); - await discordClient.sendAlert(failureMessage); + await Discord.sendAlert(failureMessage); return false; } await CiJobs.markJobAsScheduled(jobId); - await discordClient.sendDebug(`[Scheduler] Scheduled new editor image build (${jobId}).`); + await Discord.sendDebug(`[Scheduler] Scheduled new editor image build (${jobId}).`); } // The queue was not empty, so we're not happy yet diff --git a/functions/src/logic/ingestRepoVersions/index.ts b/functions/src/logic/ingestRepoVersions/index.ts index 8def854..638a633 100644 --- a/functions/src/logic/ingestRepoVersions/index.ts +++ b/functions/src/logic/ingestRepoVersions/index.ts @@ -3,16 +3,12 @@ import { Discord } from '../../service/discord'; import { scrapeVersions } from './scrapeVersions'; import { updateDatabase } from './updateDatabase'; -export const ingestRepoVersions = async ( - discordClient: Discord, - githubPrivateKey: string, - githubClientSecret: string, -) => { +export const ingestRepoVersions = async (githubPrivateKey: string, githubClientSecret: string) => { try { const scrapedInfoList = await scrapeVersions(githubPrivateKey, githubClientSecret); // Note: this triggers repoVersionInfo.onCreate modelTrigger - await updateDatabase(scrapedInfoList, discordClient); + await updateDatabase(scrapedInfoList); } catch (err: any) { const message = ` Something went wrong while importing repository versions for unity-ci/docker: @@ -20,6 +16,6 @@ export const ingestRepoVersions = async ( `; logger.error(message); - await discordClient.sendAlert(message); + await Discord.sendAlert(message); } }; diff --git a/functions/src/logic/ingestRepoVersions/updateDatabase.ts b/functions/src/logic/ingestRepoVersions/updateDatabase.ts index c65b65e..1d4ef51 100644 --- a/functions/src/logic/ingestRepoVersions/updateDatabase.ts +++ b/functions/src/logic/ingestRepoVersions/updateDatabase.ts @@ -7,10 +7,7 @@ const plural = (amount: number) => { return amount === 1 ? 'version' : 'versions'; }; -export const updateDatabase = async ( - ingestedInfoList: RepoVersionInfo[], - discordClient: Discord, -): Promise => { +export const updateDatabase = async (ingestedInfoList: RepoVersionInfo[]): Promise => { const existingInfoList = await RepoVersionInfo.getAll(); const newVersions: RepoVersionInfo[] = []; @@ -53,7 +50,7 @@ export const updateDatabase = async ( message = message.trimEnd(); if (message.length >= 1) { logger.info(message); - await discordClient.sendNews(message); + await Discord.sendNews(message); } else { logger.info('Database is up-to-date. (no updated repo versions found)'); } diff --git a/functions/src/logic/ingestUnityVersions/index.ts b/functions/src/logic/ingestUnityVersions/index.ts index 8d66a7c..59aa38a 100644 --- a/functions/src/logic/ingestUnityVersions/index.ts +++ b/functions/src/logic/ingestUnityVersions/index.ts @@ -3,12 +3,12 @@ import { logger } from 'firebase-functions/v2'; import { Discord } from '../../service/discord'; import { scrapeVersions } from './scrapeVersions'; -export const ingestUnityVersions = async (discordClient: Discord) => { +export const ingestUnityVersions = async () => { try { const scrapedInfoList = await scrapeVersions(); // Note: this triggers editorVersionInfo.onCreate modelTrigger - await updateDatabase(scrapedInfoList, discordClient); + await updateDatabase(scrapedInfoList); } catch (err: any) { const message = ` Something went wrong while importing new versions from unity: @@ -16,6 +16,6 @@ export const ingestUnityVersions = async (discordClient: Discord) => { `; logger.error(message); - await discordClient.sendAlert(message); + await Discord.sendAlert(message); } }; diff --git a/functions/src/logic/ingestUnityVersions/updateDatabase.ts b/functions/src/logic/ingestUnityVersions/updateDatabase.ts index a45d7c3..c8deecd 100644 --- a/functions/src/logic/ingestUnityVersions/updateDatabase.ts +++ b/functions/src/logic/ingestUnityVersions/updateDatabase.ts @@ -7,10 +7,7 @@ const plural = (amount: number) => { return amount === 1 ? 'version' : 'versions'; }; -export const updateDatabase = async ( - ingestedInfoList: EditorVersionInfo[], - discordClient: Discord, -): Promise => { +export const updateDatabase = async (ingestedInfoList: EditorVersionInfo[]): Promise => { const existingInfoList = await EditorVersionInfo.getAll(); const newVersions: EditorVersionInfo[] = []; @@ -50,8 +47,8 @@ export const updateDatabase = async ( message = message.trimEnd(); if (message.length >= 1) { logger.info(message); - await discordClient.sendMessageToMaintainers(message); + await Discord.sendMessageToMaintainers(message); } else { - await discordClient.sendDebug('Database is up-to-date. (no updated Unity versions found)'); + await Discord.sendDebug('Database is up-to-date. (no updated Unity versions found)'); } }; diff --git a/functions/src/model-triggers/editorVersionInfo.ts b/functions/src/model-triggers/editorVersionInfo.ts index f5eee90..7ede853 100644 --- a/functions/src/model-triggers/editorVersionInfo.ts +++ b/functions/src/model-triggers/editorVersionInfo.ts @@ -19,8 +19,7 @@ export const onCreate = onDocumentCreated( secrets: [discordToken], }, async (snapshot: FirestoreEvent) => { - const discordClient = new Discord(); - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); const editorVersionInfo = snapshot.data?.data() as EditorVersionInfo; const repoVersionInfo = await RepoVersionInfo.getLatest(); @@ -29,13 +28,13 @@ export const onCreate = onDocumentCreated( if (await CiJobs.exists(jobId)) { const message = `Skipped creating CiJob for new editorVersion (${editorVersionInfo.version}).`; logger.warn(message); - await discordClient.sendAlert(message); - await discordClient.disconnect(); + await Discord.sendAlert(message); + Discord.disconnect(); return; } await CiJobs.create(jobId, Image.types.editor, repoVersionInfo, editorVersionInfo); logger.info(`CiJob created: ${jobId}`); - await discordClient.disconnect(); + Discord.disconnect(); }, ); diff --git a/functions/src/model-triggers/repoVersionInfo.ts b/functions/src/model-triggers/repoVersionInfo.ts index bdd2062..ac90be5 100644 --- a/functions/src/model-triggers/repoVersionInfo.ts +++ b/functions/src/model-triggers/repoVersionInfo.ts @@ -23,8 +23,7 @@ export const onCreate = onDocumentCreated( secrets: [discordToken], }, async (snapshot: FirestoreEvent) => { - const discordClient = new Discord(); - await discordClient.init(discordToken.value()); + await Discord.init(discordToken.value()); const repoVersionInfo = snapshot.data?.data() as RepoVersionInfo; const currentRepoVersion = repoVersionInfo.version; @@ -36,8 +35,8 @@ export const onCreate = onDocumentCreated( Skipped scheduling all editorVersions for new repoVersion, as it does not seem to be the newest version.`; logger.warn(semverMessage); - await discordClient.sendAlert(semverMessage); - await discordClient.disconnect(); + await Discord.sendAlert(semverMessage); + Discord.disconnect(); return; } @@ -100,7 +99,7 @@ export const onCreate = onDocumentCreated( const skippedVersionsMessage = ` Skipped creating CiJobs for the following jobs \`${skippedVersions.join('`, `')}\`.`; logger.warn(skippedVersionsMessage); - await discordClient.sendAlert(skippedVersionsMessage); + await Discord.sendAlert(skippedVersionsMessage); } // Report that probably many new jobs have now been scheduled @@ -110,7 +109,7 @@ export const onCreate = onDocumentCreated( const newJobs = CiJobs.pluralise(totalNewJobs); const newJobsMessage = `Created ${newJobs} for version \`${currentRepoVersion}\` of unity-ci/docker.`; logger.info(newJobsMessage); - await discordClient.sendNews(newJobsMessage); + await Discord.sendNews(newJobsMessage); // Supersede any non-complete jobs before the current version const numSuperseded = await CiJobs.markJobsBeforeRepoVersionAsSuperseded(currentRepoVersion); @@ -118,11 +117,11 @@ export const onCreate = onDocumentCreated( const replacementMessage = ` ${CiJobs.pluralise(numSuperseded)} that were for older versions are now superseded.`; logger.warn(replacementMessage); - await discordClient.sendMessageToMaintainers(replacementMessage); + await Discord.sendMessageToMaintainers(replacementMessage); } else { logger.debug('no versions were superseded, as expected.'); } - await discordClient.disconnect(); + await Discord.disconnect(); }, ); diff --git a/functions/src/service/discord.ts b/functions/src/service/discord.ts index 36edfd7..ec8439b 100644 --- a/functions/src/service/discord.ts +++ b/functions/src/service/discord.ts @@ -2,22 +2,27 @@ import { Client as ErisClient, FileContent, Message, MessageContent } from 'eris import { logger } from 'firebase-functions/v2'; import { settings } from '../config/settings'; +let instance: ErisClient | null = null; +let instanceCount = 0; export class Discord { - instance: ErisClient | null = null; + public static async init(token: string) { + if (instance) { + instanceCount += 1; + return; + } - public async init(token: string) { - this.instance = new ErisClient(token); - this.instance.on('messageCreate', async (message: Message) => { + instance = new ErisClient(token); + instance.on('messageCreate', async (message: Message) => { if (message.content === '!ping') { logger.info('[discord] pong!'); await message.channel.createMessage('Pong!'); } }); - await this.instance.connect(); + await instance.connect(); let secondsWaited = 0; - while (!this.instance?.startTime) { + while (!instance?.startTime) { await new Promise((resolve) => setTimeout(resolve, 1000)); secondsWaited += 1; @@ -25,13 +30,15 @@ export class Discord { throw new Error('Bot never became ready'); } } + + instanceCount += 1; } - public async sendDebugLine(message: 'begin' | 'end') { - await this.instance?.createMessage(settings.discord.channels.debug, `--- ${message} ---`); + public static async sendDebugLine(message: 'begin' | 'end') { + await instance?.createMessage(settings.discord.channels.debug, `--- ${message} ---`); } - public async sendDebug( + public static async sendDebug( message: MessageContent, files: FileContent | FileContent[] | undefined = undefined, ): Promise { @@ -41,28 +48,28 @@ export class Discord { return this.sendMessage(null, message, 'info', files); } - public async sendNews( + public static async sendNews( message: MessageContent, files: FileContent | FileContent[] | undefined = undefined, ): Promise { return this.sendMessage(settings.discord.channels.news, message, 'info', files); } - public async sendAlert( + public static async sendAlert( message: MessageContent, files: FileContent | FileContent[] | undefined = undefined, ): Promise { return this.sendMessage(settings.discord.channels.alerts, message, 'error', files); } - public async sendMessageToMaintainers( + public static async sendMessageToMaintainers( message: MessageContent, files: FileContent | FileContent[] | undefined = undefined, ): Promise { return this.sendMessage(settings.discord.channels.maintainers, message, 'info', files); } - private async sendMessage( + private static async sendMessage( channelId: string | null, messageContent: MessageContent, level: 'debug' | 'info' | 'warn' | 'error' | 'critical', @@ -73,11 +80,11 @@ export class Discord { if (typeof messageContent === 'string') { for (const message of Discord.splitMessage(messageContent)) { if (channelId) { - await this.instance?.createMessage(channelId, message, files); + await instance?.createMessage(channelId, message, files); } // Also send to debug channel - await this.instance?.createMessage( + await instance?.createMessage( settings.discord.channels.debug, `[${level}] ${message}`, files, @@ -85,12 +92,12 @@ export class Discord { } } else { if (channelId) { - await this.instance?.createMessage(channelId, messageContent, files); + await instance?.createMessage(channelId, messageContent, files); } // Also send to debug channel messageContent.content = `[${level}] ${messageContent.content}`; - await this.instance?.createMessage(settings.discord.channels.debug, messageContent, files); + await instance?.createMessage(settings.discord.channels.debug, messageContent, files); } isSent = true; @@ -101,11 +108,19 @@ export class Discord { return isSent; } - public async disconnect(): Promise { - if (!this.instance) return; + public static disconnect() { + if (!instance) return; + instanceCount -= 1; + + if (instanceCount > 0) return; - this.instance.disconnect({ reconnect: false }); - this.instance = null; + instance.disconnect({ reconnect: false }); + instance = null; + + if (instanceCount < 0) { + logger.error('Discord instance count is negative! This should not happen'); + instanceCount = 0; + } } // Max message size must account for ellipsis and level parts that are added to the message.