Skip to content

Commit

Permalink
[db/server] Link workspaces and projects
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Keromnes <[email protected]>
  • Loading branch information
AlexTugarev and jankeromnes committed Jul 20, 2021
1 parent a061798 commit 845cc1f
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 83 deletions.
5 changes: 3 additions & 2 deletions components/gitpod-db/src/project-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { Project } from "@gitpod/gitpod-protocol";

export const ProjectDB = Symbol('ProjectDB');
export interface ProjectDB {
findProjectsByTeam(teamId: string): Promise<Project[]>;
findProjectByCloneUrl(cloneUrl: string): Promise<Project | undefined>;
findProjectByInstallationId(installationId: string): Promise<Project | undefined>;
createProject(name: string, cloneUrl: string, teamId: string, appInstallationId: string): Promise<Project>;
findProjectsByTeam(teamId: string): Promise<Project[]>;
storeProject(project: Project): Promise<Project>;
}
6 changes: 6 additions & 0 deletions components/gitpod-db/src/typeorm/entity/db-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export class DBWorkspace implements Workspace {
@Column("text")
contextURL: string;

@Column({
default: '',
transformer: Transformer.MAP_EMPTY_STR_TO_UNDEFINED
})
projectId?: string;

@Column()
description: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import {MigrationInterface, QueryRunner} from "typeorm";
import { tableExists, columnExists } from "./helper/helper";
import { tableExists, columnExists, indexExists } from "./helper/helper";

export class AddProjectIdToPrebuiltWorkspace1626351957505 implements MigrationInterface {

Expand All @@ -15,6 +15,9 @@ export class AddProjectIdToPrebuiltWorkspace1626351957505 implements MigrationIn
await queryRunner.query("ALTER TABLE d_b_prebuilt_workspace ADD COLUMN `projectId` char(36) DEFAULT NULL, ADD COLUMN `branch` varchar(255) DEFAULT NULL");
}
}
if (!(await indexExists(queryRunner, "d_b_project", "cloneUrl"))) {
await queryRunner.query("CREATE INDEX `ind_cloneUrl` ON `d_b_project` (cloneUrl)");
}
}

public async down(queryRunner: QueryRunner): Promise<any> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License-AGPL.txt in the project root for license information.
*/

import { MigrationInterface, QueryRunner } from "typeorm";
import { columnExists, tableExists } from "./helper/helper";

export class AddProjectIdToWorkspace1626422169862 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<any> {
if (await tableExists(queryRunner, "d_b_workspace")) {
if (!(await columnExists(queryRunner, "d_b_workspace", "projectId"))) {
await queryRunner.query("ALTER TABLE d_b_workspace ADD COLUMN `projectId` char(36)");
}
}
}

public async down(queryRunner: QueryRunner): Promise<any> {
}

}
27 changes: 10 additions & 17 deletions components/gitpod-db/src/typeorm/project-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { Repository } from "typeorm";
import { ProjectDB } from "../project-db";
import { DBProject } from "./entity/db-project";
import { Project } from "@gitpod/gitpod-protocol";
import * as uuidv4 from 'uuid/v4';

@injectable()
export class ProjectDBImpl implements ProjectDB {
Expand All @@ -24,29 +23,23 @@ export class ProjectDBImpl implements ProjectDB {
return (await this.getEntityManager()).getRepository<DBProject>(DBProject);
}

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

public async createProject(name: string, cloneUrl: string, teamId: string, appInstallationId: string): Promise<Project> {
async findProjectByInstallationId(appInstallationId: string): Promise<Project | undefined> {
const repo = await this.getRepo();
return repo.findOne({ appInstallationId });
}

const project: Project = {
id: uuidv4(),
name,
teamId,
cloneUrl,
appInstallationId,
creationTime: new Date().toISOString(),
}
await repo.save(project);
return project;
public async findProjectsByTeam(teamId: string): Promise<Project[]> {
const repo = await this.getRepo();
return repo.find({ teamId });
}

async findProjectByInstallationId(appInstallationId: string): Promise<Project | undefined> {
public async storeProject(project: Project): Promise<Project> {
const repo = await this.getRepo();
const project = await repo.findOne({ appInstallationId });
return project;
return repo.save(project);
}
}
7 changes: 5 additions & 2 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import {
WhitelistedRepository, WorkspaceImageBuild, AuthProviderInfo, Branding, CreateWorkspaceMode,
Token, UserEnvVarValue, ResolvePluginsParams, PreparePluginUploadParams, Terms,
ResolvedPlugins, Configuration, InstallPluginsParams, UninstallPluginParams, UserInfo, GitpodTokenType,
GitpodToken, AuthProviderEntry, GuessGitTokenScopesParams, GuessedGitTokenScopes, Team, TeamMemberInfo,
TeamMembershipInvite, Project, ProjectInfo, PrebuildInfo, TeamMemberRole
GitpodToken, AuthProviderEntry, GuessGitTokenScopesParams, GuessedGitTokenScopes
} from './protocol';
import {
Team, TeamMemberInfo,
TeamMembershipInvite, Project, ProjectInfo, PrebuildInfo, TeamMemberRole
} from './teams-projects-protocol';
import { JsonRpcProxy, JsonRpcServer } from './messaging/proxy-factory';
import { Disposable, CancellationTokenSource } from 'vscode-jsonrpc';
import { HeadlessLogEvent, HeadlessLogUrls } from './headless-workspace-log';
Expand Down
1 change: 1 addition & 0 deletions components/gitpod-protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './admin-protocol';
export * from './email-protocol';
export * from './headless-workspace-log';
export * from './context-url';
export * from './teams-projects-protocol';
60 changes: 2 additions & 58 deletions components/gitpod-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { WorkspaceInstance, PortVisibility } from "./workspace-instance";
import { RoleOrPermission } from "./permission";
import { Project } from "./teams-projects-protocol";

export interface UserInfo {
name?: string
Expand Down Expand Up @@ -394,6 +395,7 @@ export interface Workspace {
contextURL: string;
description: string;
ownerId: string;
projectId?: string;
context: WorkspaceContext;
config: WorkspaceConfig;

Expand Down Expand Up @@ -1201,61 +1203,3 @@ export interface Terms {
readonly content: string;
readonly formElements?: object;
}

export interface Project {
id: string;
name: string;
cloneUrl: string;
teamId: string;
appInstallationId: string;
creationTime: string;
/** This is a flag that triggers the HARD DELETION of this entity */
deleted?: boolean;
}

export interface ProjectInfo extends Project {
lastPrebuild?: PrebuildInfo;
}

export interface PrebuildInfo {
id: string;
teamId: string;
project: string;
cloneUrl: string;
branch: string;
startedAt: string;
startedBy: string;
status: PrebuiltWorkspaceState;
}

export interface Team {
id: string;
name: string;
slug: string;
creationTime: string;
/** This is a flag that triggers the HARD DELETION of this entity */
deleted?: boolean;
}

export type TeamMemberRole = "owner" | "member";

export interface TeamMemberInfo {
userId: string;
fullName?: string;
primaryEmail?: string;
avatarUrl?: string;
role: TeamMemberRole;
memberSince: string;
}

export interface TeamMembershipInvite {
id: string;
teamId: string;
role: TeamMemberRole;
creationTime: string;
invalidationTime: string;
invitedEmail?: string;

/** This is a flag that triggers the HARD DELETION of this entity */
deleted?: boolean;
}
76 changes: 76 additions & 0 deletions components/gitpod-protocol/src/teams-projects-protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License-AGPL.txt in the project root for license information.
*/

import { PrebuiltWorkspaceState } from "./protocol";
import uuidv4 = require("uuid/v4");

export interface Project {
id: string;
name: string;
cloneUrl: string;
teamId: string;
appInstallationId: string;
creationTime: string;
/** This is a flag that triggers the HARD DELETION of this entity */
deleted?: boolean;
}

export namespace Project {
export const create = (project: Omit<Project, 'id' | 'creationTime'>): Project => {
return {
...project,
id: uuidv4(),
creationTime: new Date().toISOString()
};
}
}

export interface ProjectInfo extends Project {
lastPrebuild?: PrebuildInfo;
}

export interface PrebuildInfo {
id: string;
teamId: string;
project: string;
cloneUrl: string;
branch: string;
startedAt: string;
startedBy: string;
status: PrebuiltWorkspaceState;
}

export interface Team {
id: string;
name: string;
slug: string;
creationTime: string;
/** This is a flag that triggers the HARD DELETION of this entity */
deleted?: boolean;
}

export type TeamMemberRole = "owner" | "member";

export interface TeamMemberInfo {
userId: string;
fullName?: string;
primaryEmail?: string;
avatarUrl?: string;
role: TeamMemberRole;
memberSince: string;
}

export interface TeamMembershipInvite {
id: string;
teamId: string;
role: TeamMemberRole;
creationTime: string;
invalidationTime: string;
invitedEmail?: string;

/** This is a flag that triggers the HARD DELETION of this entity */
deleted?: boolean;
}
1 change: 1 addition & 0 deletions components/server/ee/src/workspace/workspace-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
type: "regular",
creationTime: new Date().toISOString(),
contextURL: normalizedContextURL,
projectId: context.prebuiltWorkspace.projectId,
description: this.getDescription(context),
ownerId: user.id,
context: <WorkspaceContext & WithSnapshot & WithPrebuild>{
Expand Down
2 changes: 1 addition & 1 deletion components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1911,7 +1911,7 @@ export class GitpodServerImpl<Client extends GitpodClient, Server extends Gitpod
const { name, cloneUrl, teamId, appInstallationId } = params;
// Anyone who can read a team's information (i.e. any team member) can create a new project.
await this.guardTeamOperation(teamId, "get");
return this.projectDB.createProject(name, cloneUrl, teamId, appInstallationId);
return this.projectDB.storeProject(Project.create({name, cloneUrl, teamId, appInstallationId}));
}
public async getProjects(teamId: string): Promise<ProjectInfo[]> {
this.checkUser("getProjects");
Expand Down
9 changes: 7 additions & 2 deletions components/server/src/workspace/workspace-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License-AGPL.txt in the project root for license information.
*/

import { DBWithTracing, TracedWorkspaceDB, WorkspaceDB } from '@gitpod/gitpod-db/lib';
import { DBWithTracing, TracedWorkspaceDB, WorkspaceDB, ProjectDB } from '@gitpod/gitpod-db/lib';
import { CommitContext, IssueContext, PullRequestContext, Repository, SnapshotContext, User, Workspace, WorkspaceConfig, WorkspaceContext, WorkspaceProbeContext } from '@gitpod/gitpod-protocol';
import { ErrorCodes } from '@gitpod/gitpod-protocol/lib/messaging/error';
import { generateWorkspaceID } from '@gitpod/gitpod-protocol/lib/util/generate-workspace-id';
Expand All @@ -19,6 +19,7 @@ import { ImageSourceProvider } from './image-source-provider';
export class WorkspaceFactory {

@inject(TracedWorkspaceDB) protected readonly db: DBWithTracing<WorkspaceDB>;
@inject(ProjectDB) protected readonly projectDB: ProjectDB;
@inject(ConfigProvider) protected configProvider: ConfigProvider;
@inject(ImageSourceProvider) protected imageSourceProvider: ImageSourceProvider;

Expand Down Expand Up @@ -135,7 +136,10 @@ export class WorkspaceFactory {
const span = TraceContext.startSpan("createForCommit", ctx);

try {
const config = await this.configProvider.fetchConfig({span}, user, context);
const [ config, project ] = await Promise.all([
this.configProvider.fetchConfig({ span }, user, context),
this.projectDB.findProjectByCloneUrl(context.repository.cloneUrl),
]);
const imageSource = await this.imageSourceProvider.getImageSource(ctx, user, context, config);

const id = await generateWorkspaceID();
Expand All @@ -144,6 +148,7 @@ export class WorkspaceFactory {
type: "regular",
creationTime: new Date().toISOString(),
contextURL: normalizedContextURL,
projectId: project?.id,
description: this.getDescription(context),
ownerId: user.id,
context,
Expand Down

0 comments on commit 845cc1f

Please sign in to comment.