Skip to content

Commit a15a073

Browse files
committed
extract webhook related functions to scm-service.ts
1 parent e025ceb commit a15a073

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 { WorkspaceService } from "./workspace/workspace-service";
131131
import { SSHKeyService } from "./user/sshkey-service";
132132
import { GitpodTokenService } from "./user/gitpod-token-service";
133133
import { EnvVarService } from "./user/env-var-service";
134+
import { ScmService } from "./projects/scm-service";
134135

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

259260
bind(OrganizationService).toSelf().inSingletonScope();
260261
bind(ProjectsService).toSelf().inSingletonScope();
262+
bind(ScmService).toSelf().inSingletonScope();
261263

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

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

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

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

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

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

277254
this.analytics.track({
278255
userId: installer.id,
@@ -289,40 +266,6 @@ export class ProjectsService {
289266
return project;
290267
}
291268

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

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
@@ -184,6 +184,7 @@ import { SSHKeyService } from "../user/sshkey-service";
184184
import { StartWorkspaceOptions, WorkspaceService, mapGrpcError } from "./workspace-service";
185185
import { GitpodTokenService } from "../user/gitpod-token-service";
186186
import { EnvVarService } from "../user/env-var-service";
187+
import { ScmService } from "../projects/scm-service";
187188

188189
// shortcut
189190
export const traceWI = (ctx: TraceContext, wi: Omit<LogContext, "userId">) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager
@@ -243,6 +244,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
243244
@inject(HeadlessLogService) private readonly headlessLogService: HeadlessLogService,
244245

245246
@inject(ProjectsService) private readonly projectsService: ProjectsService,
247+
@inject(ScmService) private readonly scmService: ScmService,
246248

247249
@inject(IDEService) private readonly ideService: IDEService,
248250

@@ -2675,7 +2677,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
26752677

26762678
/**
26772679
* Checks if a project can be created, i.e. the current user has the required permissions
2678-
* at the given git provider.
2680+
* to install webhooks for the given repository.
26792681
*/
26802682
private async canCreateProject(currentUser: User, cloneURL: string) {
26812683
try {
@@ -2691,7 +2693,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
26912693
});
26922694
return availableRepositories.some((r) => r.cloneUrl === cloneURL);
26932695
} else {
2694-
return await this.projectsService.canCreateProject(currentUser, cloneURL);
2696+
return await this.scmService.canInstallWebhook(currentUser, cloneURL);
26952697

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

0 commit comments

Comments
 (0)