Skip to content

Commit ed2075f

Browse files
committed
extract webhook related functions to scm-service.ts
1 parent 34e1657 commit ed2075f

File tree

4 files changed

+91
-67
lines changed

4 files changed

+91
-67
lines changed

components/server/src/container-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ import { RelationshipUpdater } from "./authorization/relationship-updater";
131131
import { WorkspaceService } from "./workspace/workspace-service";
132132
import { SSHKeyService } from "./user/sshkey-service";
133133
import { GitpodTokenService } from "./user/gitpod-token-service";
134+
import { ScmService } from "./projects/scm-service";
134135

135136
export const productionContainerModule = new ContainerModule(
136137
(bind, unbind, isBound, rebind, unbindAsync, onActivation, onDeactivation) => {
@@ -257,6 +258,7 @@ export const productionContainerModule = new ContainerModule(
257258

258259
bind(OrganizationService).toSelf().inSingletonScope();
259260
bind(ProjectsService).toSelf().inSingletonScope();
261+
bind(ScmService).toSelf().inSingletonScope();
260262
bind(EnvVarService).toSelf().inSingletonScope();
261263

262264
bind(NewsletterSubscriptionController).toSelf().inSingletonScope();

components/server/src/projects/projects-service.ts

Lines changed: 8 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,23 @@ import { HostContextProvider } from "../auth/host-context-provider";
2020
import { RepoURL } from "../repohost";
2121
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
2222
import { PartialProject } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol";
23-
import { Config } from "../config";
2423
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
2524
import { ErrorCodes, ApplicationError } from "@gitpod/gitpod-protocol/lib/messaging/error";
2625
import { URL } from "url";
2726
import { Authorizer } from "../authorization/authorizer";
2827
import { TransactionalContext } from "@gitpod/gitpod-db/lib/typeorm/transactional-db-impl";
28+
import { ScmService } from "./scm-service";
2929

3030
@injectable()
3131
export class ProjectsService {
3232
constructor(
3333
@inject(ProjectDB) private readonly projectDB: ProjectDB,
3434
@inject(TracedWorkspaceDB) private readonly workspaceDb: DBWithTracing<WorkspaceDB>,
3535
@inject(HostContextProvider) private readonly hostContextProvider: HostContextProvider,
36-
@inject(Config) private readonly config: Config,
3736
@inject(IAnalyticsWriter) private readonly analytics: IAnalyticsWriter,
3837
@inject(WebhookEventDB) private readonly webhookEventDB: WebhookEventDB,
3938
@inject(Authorizer) private readonly auth: Authorizer,
39+
@inject(ScmService) private readonly scmService: ScmService,
4040
) {}
4141

4242
async getProject(userId: string, projectId: string): Promise<Project> {
@@ -194,34 +194,6 @@ export class ProjectsService {
194194
return result;
195195
}
196196

197-
async canCreateProject(currentUser: User, cloneURL: string) {
198-
try {
199-
const parsedUrl = RepoURL.parseRepoUrl(cloneURL);
200-
const hostContext = parsedUrl?.host ? this.hostContextProvider.get(parsedUrl?.host) : undefined;
201-
const authProvider = hostContext && hostContext.authProvider.info;
202-
const type = authProvider && authProvider.authProviderType;
203-
const host = authProvider?.host;
204-
if (!type || !host) {
205-
throw Error("Unknown host: " + parsedUrl?.host);
206-
}
207-
if (
208-
type === "GitLab" ||
209-
type === "Bitbucket" ||
210-
type === "BitbucketServer" ||
211-
(type === "GitHub" && (host !== "github.com" || !this.config.githubApp?.enabled))
212-
) {
213-
const repositoryService = hostContext?.services?.repositoryService;
214-
if (repositoryService) {
215-
return await repositoryService.canInstallAutomatedPrebuilds(currentUser, cloneURL);
216-
}
217-
}
218-
// The GitHub App case isn't handled here due to a circular dependency problem.
219-
} catch (error) {
220-
log.error("Failed to check precondition for creating a project.");
221-
}
222-
return false;
223-
}
224-
225197
async createProject(
226198
{ name, slug, cloneUrl, teamId, appInstallationId }: CreateProjectParams,
227199
installer: User,
@@ -273,7 +245,12 @@ export class ProjectsService {
273245
await this.auth.removeProjectFromOrg(installer.id, teamId, project.id);
274246
throw err;
275247
}
276-
await this.onDidCreateProject(project, installer);
248+
await this.scmService.installWebhookForPrebuilds(project, installer);
249+
250+
// Pre-fetch project details in the background -- don't await
251+
this.getProjectOverview(installer, project.id).catch((err) => {
252+
log.error(`Error pre-fetching project details for project ${project.id}: ${err}`);
253+
});
277254

278255
this.analytics.track({
279256
userId: installer.id,
@@ -290,40 +267,6 @@ export class ProjectsService {
290267
return project;
291268
}
292269

293-
private async onDidCreateProject(project: Project, installer: User) {
294-
// Pre-fetch project details in the background -- don't await
295-
this.getProjectOverview(installer, project.id).catch((err) => {
296-
log.error(`Error pre-fetching project details for project ${project.id}: ${err}`);
297-
});
298-
299-
// Install the prebuilds webhook if possible
300-
const { teamId, cloneUrl } = project;
301-
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
302-
const hostContext = parsedUrl?.host ? this.hostContextProvider.get(parsedUrl?.host) : undefined;
303-
const authProvider = hostContext && hostContext.authProvider.info;
304-
const type = authProvider && authProvider.authProviderType;
305-
if (
306-
type === "GitLab" ||
307-
type === "Bitbucket" ||
308-
type === "BitbucketServer" ||
309-
(type === "GitHub" && (authProvider?.host !== "github.com" || !this.config.githubApp?.enabled))
310-
) {
311-
const repositoryService = hostContext?.services?.repositoryService;
312-
if (repositoryService) {
313-
// Note: For GitLab, we expect .canInstallAutomatedPrebuilds() to always return true, because earlier
314-
// in the project creation flow, we only propose repositories where the user is actually allowed to
315-
// install a webhook.
316-
if (await repositoryService.canInstallAutomatedPrebuilds(installer, cloneUrl)) {
317-
log.info(
318-
{ organizationId: teamId, userId: installer.id },
319-
"Update prebuild installation for project.",
320-
);
321-
await repositoryService.installAutomatedPrebuilds(installer, cloneUrl);
322-
}
323-
}
324-
}
325-
}
326-
327270
async deleteProject(userId: string, projectId: string, transactionCtx?: TransactionalContext): Promise<void> {
328271
await this.auth.checkPermissionOnProject(userId, "delete", projectId);
329272

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { Project, User } from "@gitpod/gitpod-protocol";
8+
import { RepoURL } from "../repohost";
9+
import { inject, injectable } from "inversify";
10+
import { HostContextProvider } from "../auth/host-context-provider";
11+
import { Config } from "../config";
12+
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
13+
14+
@injectable()
15+
export class ScmService {
16+
constructor(
17+
@inject(HostContextProvider) private readonly hostContextProvider: HostContextProvider,
18+
@inject(Config) private readonly config: Config,
19+
) {}
20+
21+
async canInstallWebhook(currentUser: User, cloneURL: string) {
22+
try {
23+
const parsedUrl = RepoURL.parseRepoUrl(cloneURL);
24+
const hostContext = parsedUrl?.host ? this.hostContextProvider.get(parsedUrl?.host) : undefined;
25+
const authProvider = hostContext && hostContext.authProvider.info;
26+
const type = authProvider && authProvider.authProviderType;
27+
const host = authProvider?.host;
28+
if (!type || !host) {
29+
throw Error("Unknown host: " + parsedUrl?.host);
30+
}
31+
if (
32+
type === "GitLab" ||
33+
type === "Bitbucket" ||
34+
type === "BitbucketServer" ||
35+
(type === "GitHub" && (host !== "github.com" || !this.config.githubApp?.enabled))
36+
) {
37+
const repositoryService = hostContext?.services?.repositoryService;
38+
if (repositoryService) {
39+
return await repositoryService.canInstallAutomatedPrebuilds(currentUser, cloneURL);
40+
}
41+
}
42+
// The GitHub App case isn't handled here due to a circular dependency problem.
43+
} catch (error) {
44+
log.error("Failed to check precondition for creating a project.");
45+
}
46+
return false;
47+
}
48+
49+
async installWebhookForPrebuilds(project: Project, installer: User) {
50+
// Install the prebuilds webhook if possible
51+
const { teamId, cloneUrl } = project;
52+
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
53+
const hostContext = parsedUrl?.host ? this.hostContextProvider.get(parsedUrl?.host) : undefined;
54+
const authProvider = hostContext && hostContext.authProvider.info;
55+
const type = authProvider && authProvider.authProviderType;
56+
if (
57+
type === "GitLab" ||
58+
type === "Bitbucket" ||
59+
type === "BitbucketServer" ||
60+
(type === "GitHub" && (authProvider?.host !== "github.com" || !this.config.githubApp?.enabled))
61+
) {
62+
const repositoryService = hostContext?.services?.repositoryService;
63+
if (repositoryService) {
64+
// Note: For GitLab, we expect .canInstallAutomatedPrebuilds() to always return true, because earlier
65+
// in the project creation flow, we only propose repositories where the user is actually allowed to
66+
// install a webhook.
67+
if (await repositoryService.canInstallAutomatedPrebuilds(installer, cloneUrl)) {
68+
log.info(
69+
{ organizationId: teamId, userId: installer.id },
70+
"Update prebuild installation for project.",
71+
);
72+
await repositoryService.installAutomatedPrebuilds(installer, cloneUrl);
73+
}
74+
}
75+
}
76+
}
77+
}

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ import { UserService } from "../user/user-service";
191191
import { SSHKeyService } from "../user/sshkey-service";
192192
import { StartWorkspaceOptions, WorkspaceService } from "./workspace-service";
193193
import { GitpodTokenService } from "../user/gitpod-token-service";
194+
import { ScmService } from "../projects/scm-service";
194195

195196
// shortcut
196197
export const traceWI = (ctx: TraceContext, wi: Omit<LogContext, "userId">) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager
@@ -249,6 +250,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
249250
@inject(HeadlessLogService) private readonly headlessLogService: HeadlessLogService,
250251

251252
@inject(ProjectsService) private readonly projectsService: ProjectsService,
253+
@inject(ScmService) private readonly scmService: ScmService,
252254

253255
@inject(IDEService) private readonly ideService: IDEService,
254256

@@ -2825,7 +2827,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
28252827

28262828
/**
28272829
* Checks if a project can be created, i.e. the current user has the required permissions
2828-
* at the given git provider.
2830+
* to install webhooks for the given repository.
28292831
*/
28302832
private async canCreateProject(currentUser: User, cloneURL: string) {
28312833
try {
@@ -2841,7 +2843,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
28412843
});
28422844
return availableRepositories.some((r) => r.cloneUrl === cloneURL);
28432845
} else {
2844-
return await this.projectsService.canCreateProject(currentUser, cloneURL);
2846+
return await this.scmService.canInstallWebhook(currentUser, cloneURL);
28452847

28462848
// note: the GitHub App based check is not included in the ProjectService due
28472849
// to a circular dependency problem which would otherwise occur.

0 commit comments

Comments
 (0)