Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexTugarev committed Jun 29, 2021
1 parent 44fbd60 commit 6ce7174
Show file tree
Hide file tree
Showing 21 changed files with 255 additions and 310 deletions.
21 changes: 20 additions & 1 deletion components/dashboard/src/projects/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,26 @@ export default function () {
const project = projects.find(p => p.name === projectName);
if (project) {
setProject(project);
setPrebuilds(await getGitpodService().server.getPrebuilds(team.id, project.id));
// setPrebuilds(await getGitpodService().server.getPrebuilds(team.id, project.id));
setPrebuilds([{
id: "123",
branch: "feature-branch",
cloneUrl: "http://github.com/cool-test-org/foo",
startedAt: "2021-06-21T08:45:16.807Z",
startedBy: "AlexTugarev",
project: "lama",
status: "available",
teamId: "ACME"
}, {
id: "123",
branch: "feature-branch",
cloneUrl: "http://github.com/cool-test-org/foo",
startedAt: "2021-06-20T08:45:16.807Z",
startedBy: "AlexTugarev",
project: "lama",
status: "available",
teamId: "ACME"
}])
}
})();
}, [team]);
Expand Down
12 changes: 12 additions & 0 deletions components/dashboard/src/projects/Projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ export default function () {
}
(async () => {
const infos = await getGitpodService().server.getProjects(team.id);
for (const dummy of infos) {
dummy.lastPrebuild = {
id: "123",
branch: "feature-branch",
cloneUrl: "http://github.com/cool-test-org/foo",
startedAt: "2021-06-20T08:45:16.807Z",
startedBy: "AlexTugarev",
project: "lama",
status: "available",
teamId: "ACME"
}
}
setProjects(infos);
})();
}, [team]);
Expand Down
2 changes: 1 addition & 1 deletion components/ee/payment-endpoint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"docker_name": "eu.gcr.io/gitpod-dev/gitpod-payment-endpoint",
"dependencies": {
"@octokit/rest": "18.5.6",
"@octokit/webhooks": "^7.15.0",
"@octokit/webhooks": "7.21.0",
"@gitpod/gitpod-db": "0.1.5",
"@gitpod/gitpod-protocol": "0.1.5",
"body-parser": "^1.18.2",
Expand Down
1 change: 1 addition & 0 deletions components/gitpod-db/src/project-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ import { Project } from "@gitpod/gitpod-protocol";
export const ProjectDB = Symbol('ProjectDB');
export interface ProjectDB {
findProjectsByTeam(teamId: string): Promise<Project[]>;
findProjectByInstallationId(installationId: string): Promise<Project | undefined>;
createProject(name: string, cloneUrl: string, teamId: string, appInstallationId: string): Promise<Project>;
}
12 changes: 12 additions & 0 deletions components/gitpod-db/src/typeorm/entity/db-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,21 @@ export class DBWorkspace implements Workspace {
@Index()
ownerId: string;

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

@Column("text")
contextURL: string;

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

This comment has been minimized.

Copy link
@jankeromnes

jankeromnes Jul 1, 2021

Contributor

Nit: Why add branch information to all workspaces? Isn't this something we need only for prebuilds?

To me, this would make more sense if added to DBPrebuiltWorkspace instead of here:

  • E.g. you'll note that cloneURL and commit also live in DBPrebuiltWorkspace, not in workspaces

  • Also, I can't imagine what other workspace types like probes or ghosts would do with a branch info?

This comment has been minimized.

Copy link
@AlexTugarev

AlexTugarev Jul 6, 2021

Author Member

@jankeromnes the plan is to start workspaces in "context" of projects as well. See #4422, where I can select a branch and start a workspace for.


@Column()
description: string;

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 { tableExists, columnExists } from "./helper/helper";

export class AddProjectIdToWorkspace1624962141719 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) DEFAULT NULL, ADD COLUMN `branch` varchar(255) DEFAULT NULL");
}
}
}

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

}
6 changes: 6 additions & 0 deletions components/gitpod-db/src/typeorm/project-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,10 @@ export class ProjectDBImpl implements ProjectDB {
await repo.save(project);
return project;
}

async findProjectByInstallationId(appInstallationId: string): Promise<Project | undefined> {
const repo = await this.getRepo();
const project = await repo.findOne({ appInstallationId });
return project;
}
}
35 changes: 31 additions & 4 deletions components/gitpod-db/src/typeorm/workspace-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { injectable, inject } from "inversify";
import { Repository, EntityManager, DeepPartial, UpdateQueryBuilder } from "typeorm";
import { AbstractWorkspaceDB, MaybeWorkspace, MaybeWorkspaceInstance, WorkspaceDB, FindWorkspacesOptions, PrebuiltUpdatableAndWorkspace, WorkspaceInstanceSessionWithWorkspace, PrebuildWithWorkspace, WorkspaceAndOwner, WorkspacePortsAuthData, WorkspaceOwnerAndSoftDeleted } from "../workspace-db";
import { MaybeWorkspace, MaybeWorkspaceInstance, WorkspaceDB, FindWorkspacesOptions, PrebuiltUpdatableAndWorkspace, WorkspaceInstanceSessionWithWorkspace, PrebuildWithWorkspace, WorkspaceAndOwner, WorkspacePortsAuthData, WorkspaceOwnerAndSoftDeleted } from "../workspace-db";
import { Workspace, WorkspaceInstance, WorkspaceInfo, WorkspaceInstanceUser, WhitelistedRepository, Snapshot, LayoutData, PrebuiltWorkspace, RunningWorkspaceInfo, PrebuiltWorkspaceUpdatable, WorkspaceAndInstance, WorkspaceType } from "@gitpod/gitpod-protocol";
import { TypeORM } from "./typeorm";
import { DBWorkspace } from "./entity/db-workspace";
Expand All @@ -27,12 +27,10 @@ interface OrderBy {
}

@injectable()
export abstract class AbstractTypeORMWorkspaceDBImpl extends AbstractWorkspaceDB {
export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {

protected abstract getManager(): Promise<EntityManager>;

abstract transaction<T>(code: (db: WorkspaceDB) => Promise<T>): Promise<T>;

protected async getWorkspaceRepo(): Promise<Repository<DBWorkspace>> {
return await (await this.getManager()).getRepository<DBWorkspace>(DBWorkspace);
}
Expand Down Expand Up @@ -80,6 +78,23 @@ export abstract class AbstractTypeORMWorkspaceDBImpl extends AbstractWorkspaceDB
throw new Error("Could not establish connection to database!");
}

public async transaction<T>(code: (db: WorkspaceDB) => Promise<T>): Promise<T> {
return code(this);
}

async storeInstance(instance: WorkspaceInstance): Promise<WorkspaceInstance> {
const inst = await this.internalStoreInstance(instance);
return inst;
}

public async findRunningInstance(workspaceId: string): Promise<MaybeWorkspaceInstance> {
const instance = await this.findCurrentInstance(workspaceId)
if (instance && instance.status.phase !== 'stopped') {
return instance;
}
return undefined;
}

public async store(workspace: Workspace) {
const workspaceRepo = await this.getWorkspaceRepo();
const dbWorkspace = workspace as DBWorkspace;
Expand Down Expand Up @@ -798,6 +813,18 @@ export abstract class AbstractTypeORMWorkspaceDBImpl extends AbstractWorkspaceDB
return <WorkspaceAndInstance>(res);
}

async findPrebuiltWorkspacesByProject(projectId: string): Promise<PrebuiltWorkspace[]> {
const repo = await this.getPrebuiltWorkspaceRepo();

let query = await repo.createQueryBuilder('pws');
query = query.orderBy('pws.creationTime', 'ASC');
query = query.innerJoinAndMapOne('pws.workspace', DBWorkspace, 'ws', 'pws.buildWorkspaceId = ws.id');
query = query.andWhere('ws.projectId = :projectId', { projectId })

const res = await query.getMany();
return res;
}

}

@injectable()
Expand Down
79 changes: 2 additions & 77 deletions components/gitpod-db/src/workspace-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/

import { DeepPartial } from 'typeorm';
import { injectable } from 'inversify';

import { Workspace, WorkspaceInfo, WorkspaceInstance, WorkspaceInstanceUser, WhitelistedRepository, Snapshot, LayoutData, PrebuiltWorkspace, PrebuiltWorkspaceUpdatable, RunningWorkspaceInfo, WorkspaceAndInstance, WorkspaceType } from '@gitpod/gitpod-protocol';

Expand Down Expand Up @@ -112,80 +111,6 @@ export interface WorkspaceDB {
storeLayoutData(layoutData: LayoutData): Promise<LayoutData>;

hardDeleteWorkspace(workspaceID: string): Promise<void>;
}

@injectable()
export abstract class AbstractWorkspaceDB implements WorkspaceDB {
abstract connect(maxTries: number, timeout: number): Promise<void>;

abstract store(workspace: Workspace): Promise<Workspace>;
abstract updatePartial(workspaceId: string, partial: DeepPartial<Workspace>): Promise<void>;
abstract findById(id: string): Promise<MaybeWorkspace>;
abstract findByInstanceId(id: string): Promise<MaybeWorkspace>;
abstract find(options: FindWorkspacesOptions): Promise<WorkspaceInfo[]>;
abstract findWorkspacePortsAuthDataById(workspaceId: string): Promise<WorkspacePortsAuthData | undefined>;

abstract findInstanceById(workspaceInstanceId: string): Promise<MaybeWorkspaceInstance>;
abstract findInstances(workspaceId: string): Promise<WorkspaceInstance[]>;
abstract findWorkspacesByUser(userId: string): Promise<Workspace[]>;
abstract internalStoreInstance(instance: WorkspaceInstance): Promise<WorkspaceInstance>;
abstract findRegularRunningInstances(userId?: string): Promise<WorkspaceInstance[]>;
abstract findRunningInstancesWithWorkspaces(installation?: string, userId?: string): Promise<RunningWorkspaceInfo[]>;
abstract findSessionsInPeriod(userId: string, periodStart: string, periodEnd: string): Promise<WorkspaceInstanceSessionWithWorkspace[]>;
abstract findWorkspacesForGarbageCollection(minAgeInDays: number, limit: number): Promise<WorkspaceAndOwner[]>;
abstract findWorkspacesForContentDeletion(minSoftDeletedTimeInDays: number, limit: number): Promise<WorkspaceOwnerAndSoftDeleted[]>;
abstract findPrebuiltWorkspacesForGC(daysUnused: number, limit: number): Promise<WorkspaceAndOwner[]>
abstract findAllWorkspaceAndInstances(offset: number, limit: number, orderBy: keyof WorkspaceAndInstance, orderDir: "ASC" | "DESC", ownerId?: string, searchTerm?: string): Promise<{ total: number, rows: WorkspaceAndInstance[] }>;
abstract findWorkspaceAndInstance(id: string): Promise<WorkspaceAndInstance | undefined>;
abstract findAllWorkspaces(offset: number, limit: number, orderBy: keyof Workspace, orderDir: "ASC" | "DESC", ownerId?: string, searchTerm?: string, minCreationTime?: Date, maxCreationDateTime?: Date, type?: WorkspaceType): Promise<{ total: number, rows: Workspace[] }>;
abstract findAllWorkspaceInstances(offset: number, limit: number, orderBy: keyof WorkspaceInstance, orderDir: "ASC" | "DESC", ownerId?: string, minCreationTime?: Date, maxCreationTime?: Date, onlyRunning?: boolean, type?: WorkspaceType): Promise<{ total: number, rows: WorkspaceInstance[] }>;

public async transaction<T>(code: (db: WorkspaceDB) => Promise<T>): Promise<T> {
return code(this);
}

async storeInstance(instance: WorkspaceInstance): Promise<WorkspaceInstance> {
const inst = await this.internalStoreInstance(instance);
return inst;
}

abstract updateLastHeartbeat(instanceId: string, userId: string, newHeartbeat: Date, wasClosed?: boolean): Promise<void>;
abstract getLastOwnerHeartbeatFor(instance: WorkspaceInstance): Promise<{ lastSeen: Date, wasClosed?: boolean} | undefined>;
abstract getWorkspaceUsers(workspaceId: string, minLastSeen: number): Promise<WorkspaceInstanceUser[]>;
abstract updateInstancePartial(instanceId: string, partial: DeepPartial<WorkspaceInstance>): Promise<WorkspaceInstance>;

abstract findCurrentInstance(workspaceId: string): Promise<MaybeWorkspaceInstance>;

public async findRunningInstance(workspaceId: string): Promise<MaybeWorkspaceInstance> {
const instance = await this.findCurrentInstance(workspaceId)
if (instance && instance.status.phase !== 'stopped') {
return instance;
}
return undefined;
}

abstract isWhitelisted(repositoryUrl : string): Promise<boolean>;
abstract getFeaturedRepositories(): Promise<Partial<WhitelistedRepository>[]>;

abstract findSnapshotById(snapshotId: string): Promise<Snapshot | undefined>;
abstract findSnapshotsByWorkspaceId(workspaceId: string): Promise<Snapshot[]>;
abstract storeSnapshot(snapshot: Snapshot): Promise<Snapshot>;

abstract getTotalPrebuildUseSeconds(forDays: number): Promise<number | undefined>;
abstract storePrebuiltWorkspace(pws: PrebuiltWorkspace): Promise<PrebuiltWorkspace>;
abstract findPrebuiltWorkspaceByCommit(cloneURL: string, commit: string): Promise<PrebuiltWorkspace | undefined>;
abstract findPrebuildsWithWorkpace(cloneURL: string): Promise<PrebuildWithWorkspace[]>;
abstract findPrebuildByWorkspaceID(wsid: string): Promise<PrebuiltWorkspace | undefined>;
abstract findPrebuildByID(pwsid: string): Promise<PrebuiltWorkspace | undefined>;
abstract countRunningPrebuilds(cloneURL: string): Promise<number>;
abstract findQueuedPrebuilds(cloneURL?: string): Promise<PrebuildWithWorkspace[]>;
abstract attachUpdatableToPrebuild(pwsid: string, update: PrebuiltWorkspaceUpdatable): Promise<void>;
abstract findUpdatablesForPrebuild(pwsid: string): Promise<PrebuiltWorkspaceUpdatable[]>;
abstract markUpdatableResolved(updatableId: string): Promise<void>;
abstract getUnresolvedUpdatables(): Promise<PrebuiltUpdatableAndWorkspace[]>;

abstract findLayoutDataByWorkspaceId(workspaceId: string): Promise<LayoutData | undefined>;
abstract storeLayoutData(layoutData: LayoutData): Promise<LayoutData>;

abstract hardDeleteWorkspace(workspaceID: string): Promise<void>;
}
findPrebuiltWorkspacesByProject(projectId: string): Promise<PrebuiltWorkspace[]>;
}
2 changes: 2 additions & 0 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ export namespace GitpodServer {
}
export interface CreateWorkspaceOptions {
contextUrl: string;
branch?: string;
projectId?: string;

This comment has been minimized.

Copy link
@jankeromnes

jankeromnes Jul 1, 2021

Contributor

Nit: CreateWorkspaceOptions is created and sent directly from the front-end:

const result = await getGitpodService().server.createWorkspace({
contextUrl: this.props.contextUrl,
mode
});

I don't think the front-end / dashboard should be responsible for first resolving a project ID before requesting a workspace creation. However, we still wanted created workspaces to be linked to a project (e.g. so that any out-of-repo configuration can be applied).

Instead, I think the project ID should be resolved during the workspace's creation, for example during the context parsing.

This comment has been minimized.

Copy link
@AlexTugarev

AlexTugarev Jul 6, 2021

Author Member

I'm happy do discuss this, @jankeromnes!
Again, this is not how I read #4422.

mode?: CreateWorkspaceMode;
}
export interface StartWorkspaceOptions {
Expand Down
7 changes: 7 additions & 0 deletions components/gitpod-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,14 @@ export interface Workspace {
id: string;
creationTime: string;
contextURL: string;
branch?: string;

This comment has been minimized.

Copy link
@jankeromnes

jankeromnes Jul 1, 2021

Contributor

Same nit as above: Probably doesn't make sense in Workspace (because it's a Prebuild thing).

description: string;
ownerId: string;
context: WorkspaceContext;
config: WorkspaceConfig;

projectId?: string;

/**
* The source where to get the workspace base image from. This source is resolved
* during workspace creation. Once a base image has been built the information in here
Expand Down Expand Up @@ -631,6 +634,7 @@ export type PrebuiltWorkspaceState
export interface PrebuiltWorkspace {
id: string;
cloneURL: string;
branch?: string;
commit: string;
buildWorkspaceId: string;
creationTime: string;
Expand Down Expand Up @@ -765,6 +769,8 @@ export namespace ExternalImageConfigFile {
export interface WorkspaceContext {
title: string;
normalizedContextURL?: string;
branch?: string;
projectId?: string;
forceCreateNewWorkspace?: boolean;
forceImageBuild?: boolean;
}
Expand Down Expand Up @@ -814,6 +820,7 @@ export namespace SnapshotContext {

export interface StartPrebuildContext extends WorkspaceContext {
actual: WorkspaceContext;
branch?: string;
commitHistory?: string[];
}

Expand Down
2 changes: 1 addition & 1 deletion components/server/ee/src/prebuilds/bitbucket-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class BitbucketApp {
}

console.log('Starting prebuild.', { contextURL })
const ws = await this.prebuildManager.startPrebuild({ span }, user, contextURL, data.gitCloneUrl, data.commitHash);
const ws = await this.prebuildManager.startPrebuild({ span }, { user }, contextURL, data.gitCloneUrl, data.commitHash);
return ws;
} finally {
span.finish();
Expand Down
Loading

0 comments on commit 6ce7174

Please sign in to comment.