Skip to content

Commit

Permalink
[projects] lookup projects by id not cloneURL
Browse files Browse the repository at this point in the history
  • Loading branch information
svenefftinge committed Sep 22, 2023
1 parent 8db3a7c commit cc1c7cf
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 308 deletions.
3 changes: 1 addition & 2 deletions components/gitpod-db/src/project-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { TransactionalDB } from "./typeorm/transactional-db-impl";
export const ProjectDB = Symbol("ProjectDB");
export interface ProjectDB extends TransactionalDB<ProjectDB> {
findProjectById(projectId: string): Promise<Project | undefined>;
findProjectByCloneUrl(cloneUrl: string): Promise<Project | undefined>;
findProjectsByCloneUrls(cloneUrls: string[]): Promise<(Project & { teamOwners?: string[] })[]>;
findProjectsByCloneUrl(cloneUrl: string): Promise<Project[]>;
findProjects(orgID: string): Promise<Project[]>;
findProjectsBySearchTerm(
offset: number,
Expand Down
44 changes: 4 additions & 40 deletions components/gitpod-db/src/typeorm/project-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { PartialProject, Project, ProjectEnvVar, ProjectEnvVarWithValue, ProjectUsage } from "@gitpod/gitpod-protocol";
import { EncryptionService } from "@gitpod/gitpod-protocol/lib/encryption/encryption-service";
import { inject, injectable, optional } from "inversify";
import { EntityManager, Repository } from "typeorm";
import { EntityManager, FindConditions, Repository } from "typeorm";
import { v4 as uuidv4 } from "uuid";
import { ProjectDB } from "../project-db";
import { DBProject } from "./entity/db-project";
Expand Down Expand Up @@ -58,46 +58,10 @@ export class ProjectDBImpl extends TransactionalDBImpl<ProjectDB> implements Pro
return repo.findOne({ id: projectId, markedDeleted: false });
}

public async findProjectByCloneUrl(cloneUrl: string): Promise<Project | undefined> {
public async findProjectsByCloneUrl(cloneUrl: string): Promise<Project[]> {
const repo = await this.getRepo();
return repo.findOne({ cloneUrl, markedDeleted: false });
}

public async findProjectsByCloneUrls(cloneUrls: string[]): Promise<(Project & { teamOwners?: string[] })[]> {
if (cloneUrls.length === 0) {
return [];
}
const repo = await this.getRepo();
const q = repo
.createQueryBuilder("project")
.where("project.markedDeleted = false")
.andWhere(`project.cloneUrl in (${cloneUrls.map((u) => `'${u}'`).join(", ")})`);
const projects = await q.getMany();

const teamIds = Array.from(new Set(projects.map((p) => p.teamId).filter((id) => !!id)));

const teamIdsAndOwners =
teamIds.length === 0
? []
: ((await (
await this.getEntityManager()
).query(`
SELECT member.teamId AS teamId, user.name AS owner FROM d_b_user AS user
LEFT JOIN d_b_team_membership AS member ON (user.id = member.userId)
WHERE member.teamId IN (${teamIds.map((id) => `'${id}'`).join(", ")})
AND member.deleted = 0
AND member.role = 'owner'
`)) as { teamId: string; owner: string }[]);

const result: (Project & { teamOwners?: string[] })[] = [];
for (const project of projects) {
result.push({
...project,
teamOwners: teamIdsAndOwners.filter((i) => i.teamId === project.teamId).map((i) => i.owner),
});
}

return result;
const conditions: FindConditions<DBProject> = { cloneUrl, markedDeleted: false };
return repo.find(conditions);
}

public async findProjects(orgId: string): Promise<Project[]> {
Expand Down
47 changes: 12 additions & 35 deletions components/gitpod-db/src/typeorm/workspace-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
} from "./metrics";
import { TransactionalDBImpl } from "./transactional-db-impl";
import { TypeORM } from "./typeorm";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";

type RawTo<T> = (instance: WorkspaceInstance, ws: Workspace) => T;
interface OrderBy {
Expand Down Expand Up @@ -710,16 +711,19 @@ export class TypeORMWorkspaceDBImpl extends TransactionalDBImpl<WorkspaceDB> imp

// Find the (last triggered) prebuild for a given commit
public async findPrebuiltWorkspaceByCommit(
cloneURL: string,
projectId: string,
commit: string,
): Promise<PrebuiltWorkspace | undefined> {
if (!commit || !cloneURL) {
return undefined;
if (!commit || !projectId) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "Illegal arguments", { projectId, commit });
}
const repo = await this.getPrebuiltWorkspaceRepo();
return await repo
.createQueryBuilder("pws")
.where("pws.cloneURL = :cloneURL AND pws.commit LIKE :commit", { cloneURL, commit: commit + "%" })
.where("pws.projectId = :projectId AND pws.commit LIKE :commit", {
projectId,
commit: commit + "%",
})
.orderBy("pws.creationTime", "DESC")
.innerJoinAndMapOne(
"pws.workspace",
Expand Down Expand Up @@ -770,19 +774,12 @@ export class TypeORMWorkspaceDBImpl extends TransactionalDBImpl<WorkspaceDB> imp
const repo = await this.getPrebuiltWorkspaceRepo();
return await repo.findOne(pwsid);
}
public async countRunningPrebuilds(cloneURL: string): Promise<number> {
const repo = await this.getPrebuiltWorkspaceRepo();
return await repo
.createQueryBuilder("pws")
.where('pws.cloneURL = :cloneURL AND state = "building"', { cloneURL })
.getCount();
}

public async findPrebuildsWithWorkpace(cloneURL: string): Promise<PrebuildWithWorkspace[]> {
public async findPrebuildsWithWorkspace(projectId: string): Promise<PrebuildWithWorkspace[]> {
const repo = await this.getPrebuiltWorkspaceRepo();

let query = repo.createQueryBuilder("pws");
query = query.where("pws.cloneURL = :cloneURL", { cloneURL });
query = query.where("pws.projectId = :projectId", { projectId });
query = query.orderBy("pws.creationTime", "DESC");
query = query.innerJoinAndMapOne("pws.workspace", DBWorkspace, "ws", "pws.buildWorkspaceId = ws.id");
query = query.andWhere("ws.deleted = false");
Expand All @@ -798,37 +795,17 @@ export class TypeORMWorkspaceDBImpl extends TransactionalDBImpl<WorkspaceDB> imp
});
}

public async countUnabortedPrebuildsSince(cloneURL: string, date: Date): Promise<number> {
public async countUnabortedPrebuildsSince(projectId: string, date: Date): Promise<number> {
const abortedState: PrebuiltWorkspaceState = "aborted";
const repo = await this.getPrebuiltWorkspaceRepo();

let query = repo.createQueryBuilder("pws");
query = query.where("pws.cloneURL = :cloneURL", { cloneURL });
query = query.where("pws.projectId != :projectId", { projectId });
query = query.andWhere("pws.creationTime >= :time", { time: date.toISOString() });
query = query.andWhere("pws.state != :state", { state: abortedState });
return query.getCount();
}

public async findQueuedPrebuilds(cloneURL?: string): Promise<PrebuildWithWorkspace[]> {
const repo = await this.getPrebuiltWorkspaceRepo();

let query = await repo.createQueryBuilder("pws");
query = query.where('state = "queued"');
if (cloneURL) {
query = query.andWhere("pws.cloneURL = :cloneURL", { cloneURL });
}
query = query.orderBy("pws.creationTime", "ASC");
query = query.innerJoinAndMapOne("pws.workspace", DBWorkspace, "ws", "pws.buildWorkspaceId = ws.id");

const res = await query.getMany();
return res.map((r) => {
const withWorkspace: PrebuiltWorkspace & { workspace: Workspace } = r as any;
return {
prebuild: r,
workspace: withWorkspace.workspace,
};
});
}
public async attachUpdatableToPrebuild(pwsid: string, update: PrebuiltWorkspaceUpdatable): Promise<void> {
const repo = await this.getPrebuiltWorkspaceUpdatableRepo();
await repo.save(update);
Expand Down
18 changes: 17 additions & 1 deletion components/gitpod-db/src/workspace-db.spec.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { TypeORM } from "./typeorm/typeorm";
import { DBPrebuiltWorkspace } from "./typeorm/entity/db-prebuilt-workspace";
import { secondsBefore } from "@gitpod/gitpod-protocol/lib/util/timeutil";
import { resetDB } from "./test/reset-db";
import { v4 } from "uuid";

@suite
class WorkspaceDBSpec {
Expand Down Expand Up @@ -508,6 +509,7 @@ class WorkspaceDBSpec {
public async testCountUnabortedPrebuildsSince() {
const now = new Date();
const cloneURL = "https://github.com/gitpod-io/gitpod";
const projectId = v4();

await Promise.all([
// Created now, and queued
Expand All @@ -516,6 +518,7 @@ class WorkspaceDBSpec {
buildWorkspaceId: "apples",
creationTime: now.toISOString(),
cloneURL: cloneURL,
projectId,
commit: "",
state: "queued",
statusVersion: 0,
Expand All @@ -526,6 +529,7 @@ class WorkspaceDBSpec {
buildWorkspaceId: "bananas",
creationTime: now.toISOString(),
cloneURL: cloneURL,
projectId,
commit: "",
state: "aborted",
statusVersion: 0,
Expand All @@ -536,14 +540,26 @@ class WorkspaceDBSpec {
buildWorkspaceId: "oranges",
creationTime: secondsBefore(now.toISOString(), 62),
cloneURL: cloneURL,
projectId,
commit: "",
state: "available",
statusVersion: 0,
}),
// different project now and queued
this.storePrebuiltWorkspace({
id: "prebuild123-other",
buildWorkspaceId: "apples",
creationTime: now.toISOString(),
cloneURL: cloneURL,
projectId: "other-projectId",
commit: "",
state: "queued",
statusVersion: 0,
}),
]);

const minuteAgo = secondsBefore(now.toISOString(), 60);
const unabortedCount = await this.db.countUnabortedPrebuildsSince(cloneURL, new Date(minuteAgo));
const unabortedCount = await this.db.countUnabortedPrebuildsSince(projectId, new Date(minuteAgo));
expect(unabortedCount).to.eq(1);
}

Expand Down
9 changes: 3 additions & 6 deletions components/gitpod-db/src/workspace-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export interface WorkspaceDB {
findInstancesByPhase(phases: string[]): Promise<WorkspaceInstance[]>;

getWorkspaceCount(type?: String): Promise<Number>;
getWorkspaceCountByCloneURL(cloneURL: string, sinceLastDays?: number, type?: string): Promise<number>;
getInstanceCount(type?: string): Promise<number>;

findRegularRunningInstances(userId?: string): Promise<WorkspaceInstance[]>;
Expand All @@ -158,17 +157,15 @@ export interface WorkspaceDB {
updateSnapshot(snapshot: DeepPartial<Snapshot> & Pick<Snapshot, "id">): Promise<void>;

storePrebuiltWorkspace(pws: PrebuiltWorkspace): Promise<PrebuiltWorkspace>;
findPrebuiltWorkspaceByCommit(cloneURL: string, commit: string): Promise<PrebuiltWorkspace | undefined>;
findPrebuiltWorkspaceByCommit(projectId: string, commit: string): Promise<PrebuiltWorkspace | undefined>;
findActivePrebuiltWorkspacesByBranch(
projectId: string,
branch: string,
): Promise<PrebuildWithWorkspaceAndInstances[]>;
findPrebuildsWithWorkpace(cloneURL: string): Promise<PrebuildWithWorkspace[]>;
findPrebuildsWithWorkspace(projectId: string): Promise<PrebuildWithWorkspace[]>;
findPrebuildByWorkspaceID(wsid: string): Promise<PrebuiltWorkspace | undefined>;
findPrebuildByID(pwsid: string): Promise<PrebuiltWorkspace | undefined>;
countRunningPrebuilds(cloneURL: string): Promise<number>;
countUnabortedPrebuildsSince(cloneURL: string, date: Date): Promise<number>;
findQueuedPrebuilds(cloneURL?: string): Promise<PrebuildWithWorkspace[]>;
countUnabortedPrebuildsSince(projectId: string, date: Date): Promise<number>;
attachUpdatableToPrebuild(pwsid: string, update: PrebuiltWorkspaceUpdatable): Promise<void>;
findUpdatablesForPrebuild(pwsid: string): Promise<PrebuiltWorkspaceUpdatable[]>;
markUpdatableResolved(updatableId: string): Promise<void>;
Expand Down
3 changes: 1 addition & 2 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,6 @@ export interface ProviderRepository {
updatedAt?: string;
installationId?: number;
installationUpdatedAt?: string;

inUse?: { userName: string };
}

export interface ClientHeaderFields {
Expand Down Expand Up @@ -418,6 +416,7 @@ export namespace GitpodServer {
export interface CreateWorkspaceOptions extends StartWorkspaceOptions {
contextUrl: string;
organizationId: string;
projectId?: string;

// whether running workspaces on the same context should be ignored. If false (default) users will be asked.
//TODO(se) remove this option and let clients do that check if they like. The new create workspace page does it already
Expand Down
33 changes: 18 additions & 15 deletions components/server/src/prebuilds/bitbucket-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import express from "express";
import { postConstruct, injectable, inject } from "inversify";
import { ProjectDB, TeamDB, WebhookEventDB } from "@gitpod/gitpod-db/lib";
import { TeamDB, WebhookEventDB } from "@gitpod/gitpod-db/lib";
import { User, StartPrebuildResult, CommitContext, CommitInfo, Project, WebhookEvent } from "@gitpod/gitpod-protocol";
import { PrebuildManager } from "./prebuild-manager";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
Expand All @@ -18,19 +18,21 @@ import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { UserService } from "../user/user-service";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { URL } from "url";
import { ProjectsService } from "../projects/projects-service";

@injectable()
export class BitbucketApp {
@inject(UserService) protected readonly userService: UserService;
@inject(PrebuildManager) protected readonly prebuildManager: PrebuildManager;
@inject(TokenService) protected readonly tokenService: TokenService;
@inject(ProjectDB) protected readonly projectDB: ProjectDB;
@inject(TeamDB) protected readonly teamDB: TeamDB;
@inject(ContextParser) protected readonly contextParser: ContextParser;
@inject(HostContextProvider) protected readonly hostCtxProvider: HostContextProvider;
@inject(WebhookEventDB) protected readonly webhookEvents: WebhookEventDB;
constructor(
@inject(UserService) private readonly userService: UserService,
@inject(PrebuildManager) private readonly prebuildManager: PrebuildManager,
@inject(TeamDB) private readonly teamDB: TeamDB,
@inject(ContextParser) private readonly contextParser: ContextParser,
@inject(HostContextProvider) private readonly hostCtxProvider: HostContextProvider,
@inject(WebhookEventDB) private readonly webhookEvents: WebhookEventDB,
@inject(ProjectsService) private readonly projectService: ProjectsService,
) {}

protected _router = express.Router();
private _router = express.Router();
public static path = "/apps/bitbucket/";

@postConstruct()
Expand Down Expand Up @@ -82,7 +84,7 @@ export class BitbucketApp {
});
}

protected async findUser(ctx: TraceContext, secretToken: string): Promise<User> {
private async findUser(ctx: TraceContext, secretToken: string): Promise<User> {
const span = TraceContext.startSpan("BitbucketApp.findUser", ctx);
try {
span.setTag("secret-token", secretToken);
Expand All @@ -108,7 +110,7 @@ export class BitbucketApp {
}
}

protected async handlePushHook(
private async handlePushHook(
ctx: TraceContext,
data: ParsedRequestData,
user: User,
Expand Down Expand Up @@ -199,12 +201,13 @@ export class BitbucketApp {
* @param webhookInstaller the user account known from the webhook installation
* @returns a promise which resolves to a user account and an optional project.
*/
protected async findProjectAndOwner(
private async findProjectAndOwner(
cloneURL: string,
webhookInstaller: User,
): Promise<{ user: User; project?: Project }> {
try {
const project = await this.projectDB.findProjectByCloneUrl(cloneURL);
const projects = await this.projectService.findProjectsByCloneUrl(webhookInstaller.id, cloneURL);
const project = projects[0];
if (project) {
if (!project.teamId) {
throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "Project has no teamId");
Expand All @@ -228,7 +231,7 @@ export class BitbucketApp {
return { user: webhookInstaller };
}

protected createContextUrl(data: ParsedRequestData) {
private createContextUrl(data: ParsedRequestData) {
const contextUrl = `${data.repoUrl}/src/${data.commitHash}/?at=${encodeURIComponent(data.branchName)}`;
return contextUrl;
}
Expand Down
Loading

0 comments on commit cc1c7cf

Please sign in to comment.