From a59ee55427329a7f4a8b772b6707ad326fa9e276 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Wed, 11 May 2022 13:46:54 +0000 Subject: [PATCH] [prebuilds] no prebuilds for inactive repos --- .../src/typeorm/workspace-db-impl.ts | 16 +++++++ .../gitpod-db/src/workspace-db.spec.db.ts | 48 ++++++++++++++++++- components/gitpod-db/src/workspace-db.ts | 1 + .../ee/src/prebuilds/prebuild-manager.ts | 19 ++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/components/gitpod-db/src/typeorm/workspace-db-impl.ts b/components/gitpod-db/src/typeorm/workspace-db-impl.ts index e37a12849ecd15..d84d316e51bf70 100644 --- a/components/gitpod-db/src/typeorm/workspace-db-impl.ts +++ b/components/gitpod-db/src/typeorm/workspace-db-impl.ts @@ -356,6 +356,22 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB { return workspaceRepo.find({ ownerId: userId }); } + public async getWorkspaceCountByCloneURL( + cloneURL: string, + sinceLastDays: number = 7, + type: string = "regular", + ): Promise { + const workspaceRepo = await this.getWorkspaceRepo(); + const since = new Date(); + since.setDate(since.getDate() - sinceLastDays); + return workspaceRepo + .createQueryBuilder("ws") + .where('context->"$.repository.cloneUrl" = :cloneURL', { cloneURL }) + .andWhere("creationTime > :since", { since: since.toISOString() }) + .andWhere("type = :type", { type }) + .getCount(); + } + public async findCurrentInstance(workspaceId: string): Promise { const workspaceInstanceRepo = await this.getWorkspaceInstanceRepo(); const qb = workspaceInstanceRepo diff --git a/components/gitpod-db/src/workspace-db.spec.db.ts b/components/gitpod-db/src/workspace-db.spec.db.ts index 5cb2dec8d02e05..cf49bd6fc375e3 100644 --- a/components/gitpod-db/src/workspace-db.spec.db.ts +++ b/components/gitpod-db/src/workspace-db.spec.db.ts @@ -9,7 +9,7 @@ const expect = chai.expect; import { suite, test, timeout } from "mocha-typescript"; import { fail } from "assert"; -import { WorkspaceInstance, Workspace, PrebuiltWorkspace } from "@gitpod/gitpod-protocol"; +import { WorkspaceInstance, Workspace, PrebuiltWorkspace, CommitContext } from "@gitpod/gitpod-protocol"; import { testContainer } from "./test-container"; import { TypeORMWorkspaceDBImpl } from "./typeorm/workspace-db-impl"; import { TypeORM } from "./typeorm/typeorm"; @@ -539,6 +539,52 @@ class WorkspaceDBSpec { expect(unabortedCount).to.eq(1); } + @test(timeout(10000)) + public async testGetWorkspaceCountForCloneURL() { + const now = new Date(); + const eightDaysAgo = new Date(); + eightDaysAgo.setDate(eightDaysAgo.getDate() - 8); + const activeRepo = "http://github.com/myorg/active.git"; + const inactiveRepo = "http://github.com/myorg/inactive.git"; + await Promise.all([ + this.db.store({ + id: "12345", + creationTime: eightDaysAgo.toISOString(), + description: "something", + contextURL: "http://github.com/myorg/inactive", + ownerId: "1221423", + context: { + title: "my title", + repository: { + cloneUrl: inactiveRepo, + }, + }, + config: {}, + type: "regular", + }), + this.db.store({ + id: "12346", + creationTime: now.toISOString(), + description: "something", + contextURL: "http://github.com/myorg/active", + ownerId: "1221423", + context: { + title: "my title", + repository: { + cloneUrl: activeRepo, + }, + }, + config: {}, + type: "regular", + }), + ]); + + const inactiveCount = await this.db.getWorkspaceCountByCloneURL(inactiveRepo, 7, "regular"); + expect(inactiveCount).to.eq(0, "there should be no regular workspaces in the past 7 days"); + const activeCount = await this.db.getWorkspaceCountByCloneURL(activeRepo, 7, "regular"); + expect(activeCount).to.eq(1, "there should be exactly one regular workspace"); + } + private async storePrebuiltWorkspace(pws: PrebuiltWorkspace) { // store the creationTime directly, before it is modified by the store function in the ORM layer const creationTime = pws.creationTime; diff --git a/components/gitpod-db/src/workspace-db.ts b/components/gitpod-db/src/workspace-db.ts index 7eae4b91935d29..d7838973b61158 100644 --- a/components/gitpod-db/src/workspace-db.ts +++ b/components/gitpod-db/src/workspace-db.ts @@ -125,6 +125,7 @@ export interface WorkspaceDB { findInstancesByPhaseAndRegion(phase: string, region: string): Promise; getWorkspaceCount(type?: String): Promise; + getWorkspaceCountByCloneURL(cloneURL: string, sinceLastDays?: number, type?: string): Promise; getInstanceCount(type?: string): Promise; findAllWorkspaceInstances( diff --git a/components/server/ee/src/prebuilds/prebuild-manager.ts b/components/server/ee/src/prebuilds/prebuild-manager.ts index 3575a1b348f6dc..c52b5b135dfbfe 100644 --- a/components/server/ee/src/prebuilds/prebuild-manager.ts +++ b/components/server/ee/src/prebuilds/prebuild-manager.ts @@ -195,6 +195,11 @@ export class PrebuildManager { prebuild.error = "Project is inactive. Please start a new workspace for this project to re-enable prebuilds."; await this.workspaceDB.trace({ span }).storePrebuiltWorkspace(prebuild); + } else if (!project && (await this.shouldSkipInactiveRepository({ span }, cloneURL))) { + prebuild.state = "aborted"; + prebuild.error = + "Repository is inactive. Please create a project for this repository to re-enable prebuilds."; + await this.workspaceDB.trace({ span }).storePrebuiltWorkspace(prebuild); } else { span.setTag("starting", true); const projectEnvVars = await projectEnvVarsPromise; @@ -356,4 +361,18 @@ export class PrebuildManager { const inactiveProjectTime = 1000 * 60 * 60 * 24 * 7 * 1; // 1 week return now - lastUse > inactiveProjectTime; } + + private async shouldSkipInactiveRepository(ctx: TraceContext, cloneURL: string): Promise { + const span = TraceContext.startSpan("shouldSkipInactiveRepository", ctx); + try { + return ( + (await this.workspaceDB + .trace({ span }) + .getWorkspaceCountByCloneURL(cloneURL, 7 /* last week */, "regular")) === 0 + ); + } catch (error) { + log.error("cannot compute activity for repository", { cloneURL }, error); + return false; + } + } }