Skip to content

Commit 5cfebd8

Browse files
committed
debug: enable webhook on newly enabled prebuilds
1 parent d99c1af commit 5cfebd8

File tree

4 files changed

+84
-2
lines changed

4 files changed

+84
-2
lines changed

components/server/src/prebuilds/github-service.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ import { Config } from "../config";
1414
import { RepoURL } from "../repohost";
1515
import { UnauthorizedError } from "../errors";
1616
import { GitHubOAuthScopes } from "@gitpod/public-api-common/lib/auth-providers";
17+
import { containsScopes } from "./token-scopes-inclusion";
18+
import { TokenService } from "../user/token-service";
19+
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
1720

1821
@injectable()
1922
export class GitHubService extends RepositoryService {
2023
constructor(
2124
@inject(GitHubRestApi) protected readonly githubApi: GitHubRestApi,
2225
@inject(Config) private readonly config: Config,
26+
@inject(TokenService) private readonly tokenService: TokenService,
2327
@inject(GithubContextParser) private readonly githubContextParser: GithubContextParser,
2428
) {
2529
super();
@@ -44,6 +48,53 @@ export class GitHubService extends RepositoryService {
4448
}
4549
}
4650

51+
async installAutomatedPrebuilds(user: User, cloneUrl: string): Promise<void> {
52+
const parsedRepoUrl = RepoURL.parseRepoUrl(cloneUrl);
53+
if (!parsedRepoUrl) {
54+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, `Clone URL not parseable.`);
55+
}
56+
let tokenEntry;
57+
try {
58+
const { owner, repoName: repo } = await this.githubContextParser.parseURL(user, cloneUrl);
59+
const webhooks = (await this.githubApi.run(user, (gh) => gh.repos.listWebhooks({ owner, repo }))).data;
60+
for (const webhook of webhooks) {
61+
if (webhook.config.url === this.getHookUrl()) {
62+
await this.githubApi.run(user, (gh) =>
63+
gh.repos.deleteWebhook({ owner, repo, hook_id: webhook.id }),
64+
);
65+
}
66+
}
67+
tokenEntry = await this.tokenService.createGitpodToken(user, "prebuild", cloneUrl);
68+
const config = {
69+
url: this.getHookUrl(),
70+
content_type: "json",
71+
secret: user.id + "|" + tokenEntry.token.value,
72+
};
73+
await this.githubApi.run(user, (gh) => gh.repos.createWebhook({ owner, repo, config }));
74+
} catch (error) {
75+
// Hint: here we catch all GH API errors to forward them as Unauthorized to FE,
76+
// eventually that should be done depending on the error code.
77+
// Also, if user is not connected at all, then the GH API wrapper is throwing
78+
// the same error type, but with `providerIsConnected: false`.
79+
80+
if (GitHubApiError.is(error)) {
81+
// TODO check for `error.code`
82+
throw UnauthorizedError.create({
83+
host: parsedRepoUrl.host,
84+
providerType: "GitHub",
85+
repoName: parsedRepoUrl.repo,
86+
requiredScopes: GitHubOAuthScopes.Requirements.PRIVATE_REPO,
87+
providerIsConnected: true,
88+
isMissingScopes: containsScopes(
89+
tokenEntry?.token.scopes,
90+
GitHubOAuthScopes.Requirements.PRIVATE_REPO,
91+
),
92+
});
93+
}
94+
throw error;
95+
}
96+
}
97+
4798
protected getHookUrl() {
4899
return this.config.hostUrl
49100
.asPublicServices()

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type { PrebuildManager } from "../prebuilds/prebuild-manager";
3939
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
4040
import { ContextParser } from "../workspace/context-parser-service";
4141
import { UnauthorizedError } from "../errors";
42+
import { ScmService } from "../scm/scm-service";
4243

4344
// to resolve circular dependency issues
4445
export const LazyPrebuildManager = Symbol("LazyPrebuildManager");
@@ -58,6 +59,7 @@ export class ProjectsService {
5859
@inject(LazyPrebuildManager) private readonly prebuildManager: LazyPrebuildManager,
5960
@inject(ContextParser) private readonly contextParser: ContextParser,
6061
@inject(WebhookEventDB) private readonly webhookEventDb: WebhookEventDB,
62+
@inject(ScmService) private readonly scmService: ScmService,
6163

6264
@inject(InstallationService) private readonly installationService: InstallationService,
6365
) {}
@@ -431,7 +433,8 @@ export class ProjectsService {
431433
const enablePrebuildsPrev = !!existingProject.settings?.prebuilds?.enable;
432434
if (!enablePrebuildsPrev) {
433435
// new default
434-
partialProject.settings.prebuilds.triggerStrategy = "activity-based";
436+
await this.scmService.installWebhookForPrebuilds(existingProject, user);
437+
partialProject.settings.prebuilds.triggerStrategy = "webhook-based";
435438
}
436439
}
437440
return this.projectDB.updateProject(partialProject);

components/server/src/repohost/repo-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ export class RepositoryService {
1212
async isGitpodWebhookEnabled(user: User, cloneUrl: string): Promise<boolean> {
1313
throw new Error("unsupported");
1414
}
15+
16+
async installAutomatedPrebuilds(user: User, cloneUrl: string): Promise<void> {
17+
throw new Error("unsupported");
18+
}
1519
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { inject, injectable } from "inversify";
88
import { Authorizer } from "../authorization/authorizer";
99
import { Config } from "../config";
1010
import { TokenProvider } from "../user/token-provider";
11-
import { CommitContext, Project, SuggestedRepository, Token, WorkspaceInfo } from "@gitpod/gitpod-protocol";
11+
import { CommitContext, Project, SuggestedRepository, Token, User, WorkspaceInfo } from "@gitpod/gitpod-protocol";
1212
import { HostContextProvider } from "../auth/host-context-provider";
1313
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1414
import { AuthProviderService } from "../auth/auth-provider-service";
@@ -22,6 +22,7 @@ import {
2222
suggestionFromRecentWorkspace,
2323
suggestionFromUserRepo,
2424
} from "../workspace/suggested-repos-sorter";
25+
import { RepoURL } from "../repohost";
2526

2627
@injectable()
2728
export class ScmService {
@@ -52,6 +53,29 @@ export class ScmService {
5253
return token;
5354
}
5455

56+
public async installWebhookForPrebuilds(project: Project, installer: User) {
57+
// Install the prebuilds webhook if possible
58+
const { teamId, cloneUrl } = project;
59+
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
60+
const hostContext = parsedUrl?.host ? this.hostContextProvider.get(parsedUrl?.host) : undefined;
61+
62+
if (!hostContext) {
63+
throw new ApplicationError(ErrorCodes.NOT_FOUND, `SCM provider not found.`);
64+
}
65+
66+
const repositoryService = hostContext.services?.repositoryService;
67+
if (repositoryService) {
68+
const logPayload = { organizationId: teamId, installer: installer.id, cloneUrl: project.cloneUrl };
69+
try {
70+
await repositoryService.installAutomatedPrebuilds(installer, cloneUrl);
71+
log.info("Webhook for prebuilds installed.", logPayload);
72+
} catch (error) {
73+
log.error("Failed to install webhook for prebuilds.", error, logPayload);
74+
throw error;
75+
}
76+
}
77+
}
78+
5579
/**
5680
* `guessTokenScopes` allows clients to retrieve scopes that would be necessary for a specified
5781
* git operation on a specified repository.

0 commit comments

Comments
 (0)