Skip to content

Commit 981c285

Browse files
committed
[gitlab] bump gitlab API library
this also adds test for changed API and a test to verify that `Projects.all` is paginated as expected.
1 parent 44b1dca commit 981c285

File tree

6 files changed

+264
-70
lines changed

6 files changed

+264
-70
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (c) 2022 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 { User } from "@gitpod/gitpod-protocol";
8+
import { skipIfEnvVarNotSet } from "@gitpod/gitpod-protocol/lib/util/skip-if";
9+
import { expect } from "chai";
10+
import { Container, ContainerModule } from "inversify";
11+
import { suite, retries, test, timeout } from "mocha-typescript";
12+
import { AuthProviderParams } from "../../../src/auth/auth-provider";
13+
import { DevData } from "../../../src/dev/dev-data";
14+
import { GitLabApi } from "../../../src/gitlab/api";
15+
import { GitLabTokenHelper } from "../../../src/gitlab/gitlab-token-helper";
16+
import { TokenProvider } from "../../../src/user/token-provider";
17+
import { GitLabAppSupport } from "./gitlab-app-support";
18+
19+
@suite(timeout(10000), retries(2), skipIfEnvVarNotSet("GITPOD_TEST_TOKEN_GITLAB"))
20+
class TestGitLabAppSupport {
21+
static readonly AUTH_HOST_CONFIG: Partial<AuthProviderParams> = {
22+
id: "Public-GitLab",
23+
type: "GitLab",
24+
verified: true,
25+
description: "",
26+
icon: "",
27+
host: "gitlab.com",
28+
};
29+
30+
protected appSupport: GitLabAppSupport;
31+
protected user: User;
32+
protected container: Container;
33+
34+
public before() {
35+
this.container = new Container();
36+
this.container.load(
37+
new ContainerModule((bind, unbind, isBound, rebind) => {
38+
bind(GitLabApi).toSelf().inSingletonScope();
39+
bind(AuthProviderParams).toConstantValue(TestGitLabAppSupport.AUTH_HOST_CONFIG);
40+
bind(GitLabTokenHelper).toSelf().inSingletonScope();
41+
bind(TokenProvider).toConstantValue(<TokenProvider>{
42+
getTokenForHost: async () => DevData.createGitlabTestToken(),
43+
getFreshPortAuthenticationToken: async (user: User, workspaceId: string) =>
44+
DevData.createPortAuthTestToken(workspaceId),
45+
});
46+
bind(GitLabAppSupport).toSelf().inSingletonScope();
47+
}),
48+
);
49+
this.appSupport = this.container.get(GitLabAppSupport);
50+
this.user = DevData.createTestUser();
51+
}
52+
53+
// this manual test is assume you've maintainer access to +200 project
54+
// see createManyRepos in /workspace/gitpod/components/server/src/gitlab/gitlab-file-provider.spec.ts
55+
// for how to set up a test group with that many projects ;-)
56+
@test.skip public async testFindMoreThan200Projects() {
57+
const result = await this.appSupport.getProviderRepositoriesForUser({
58+
user: this.user,
59+
provider: {
60+
host: "gitlab.com",
61+
authProviderId: "Public-GitLab",
62+
authProviderType: "GitLab",
63+
verified: true,
64+
},
65+
});
66+
expect(result.length).to.be.greaterThan(200);
67+
}
68+
}
69+
70+
module.exports = new TestGitLabAppSupport();

components/server/ee/src/gitlab/gitlab-app-support.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,11 @@
77
import { AuthProviderInfo, ProviderRepository, User } from "@gitpod/gitpod-protocol";
88
import { inject, injectable } from "inversify";
99
import { TokenProvider } from "../../../src/user/token-provider";
10-
import { UserDB } from "@gitpod/gitpod-db/lib";
1110
import { Gitlab } from "@gitbeaker/node";
12-
import { ProjectSchemaDefault, NamespaceInfoSchemaDefault } from "@gitbeaker/core/dist/types/services/Projects";
13-
14-
// Add missing fields to Gitbeaker's ProjectSchema type
15-
type ProjectSchema = ProjectSchemaDefault & {
16-
last_activity_at: string;
17-
namespace: NamespaceInfoSchemaDefault & {
18-
avatar_url: string | null;
19-
parent_id: number | null;
20-
};
21-
owner?: {
22-
id: number;
23-
name: string;
24-
avatar_url: string | null;
25-
};
26-
};
11+
import { GitLab } from "../../../src/gitlab/api";
2712

2813
@injectable()
2914
export class GitLabAppSupport {
30-
@inject(UserDB) protected readonly userDB: UserDB;
3115
@inject(TokenProvider) protected readonly tokenProvider: TokenProvider;
3216

3317
async getProviderRepositoriesForUser(params: {
@@ -51,9 +35,12 @@ export class GitLabAppSupport {
5135
// we are listing only those projects with access level of maintainers.
5236
// also cf. https://docs.gitlab.com/ee/api/members.html#valid-access-levels
5337
//
54-
const projectsWithAccess = await api.Projects.all({ min_access_level: "40", perPage: 100 });
38+
const projectsWithAccess = await api.Projects.all({
39+
min_access_level: "40",
40+
perPage: 100,
41+
});
5542
for (const project of projectsWithAccess) {
56-
const aProject = project as ProjectSchema;
43+
const aProject = project as GitLab.Project;
5744
const path = aProject.path as string;
5845
const fullPath = aProject.path_with_namespace as string;
5946
const cloneUrl = aProject.http_url_to_repo as string;
@@ -77,7 +64,7 @@ export class GitLabAppSupport {
7764
return result;
7865
}
7966

80-
protected async getAccountAvatarUrl(project: ProjectSchema, providerHost: string): Promise<string> {
67+
protected async getAccountAvatarUrl(project: GitLab.Project, providerHost: string): Promise<string> {
8168
let owner = project.owner;
8269
if (!owner && project.namespace && !project.namespace.parent_id) {
8370
// Fall back to "root namespace" / "top-level group"

components/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"/dist"
2828
],
2929
"dependencies": {
30-
"@gitbeaker/node": "^25.6.0",
30+
"@gitbeaker/node": "^35.7.0",
3131
"@gitpod/content-service": "0.1.5",
3232
"@gitpod/gitpod-db": "0.1.5",
3333
"@gitpod/gitpod-messagebus": "0.1.5",

components/server/src/gitlab/api.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import {
2020
Issues,
2121
RepositoryFiles,
2222
} from "@gitbeaker/core";
23-
import { ProjectSchemaDefault } from "@gitbeaker/core/dist/types/services/Projects";
24-
import { UserSchemaDefault } from "@gitbeaker/core/dist/types/services/Users";
23+
import { ProjectExtendedSchema } from "@gitbeaker/core/dist/types/resources/Projects";
24+
import { NamespaceSchema } from "@gitbeaker/core/dist/types/resources/Namespaces";
25+
import { UserExtendedSchema, UserSchema } from "@gitbeaker/core/dist/types/resources/Users";
2526
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
2627
import { GitLabScope } from "./scopes";
2728
import { AuthProviderParams } from "../auth/auth-provider";
@@ -70,9 +71,12 @@ export class GitLabApi {
7071
// "description": "404 Commit Not Found"
7172
// }
7273

73-
return new GitLab.ApiError(`GitLab Request Error: ${error?.description}`, error);
74+
return new GitLab.ApiError(
75+
`GitLab Request Error: ${error?.description?.name || JSON.stringify(error?.description)}`,
76+
error,
77+
);
7478
}
75-
log.error(`GitLab request error`, error);
79+
// log.error(`GitLab request error`, error);
7680
throw error;
7781
} finally {
7882
log.debug(`GitLab request took ${new Date().getTime() - before} ms`);
@@ -87,7 +91,9 @@ export class GitLabApi {
8791
path: string,
8892
): Promise<string | undefined> {
8993
const projectId = `${org}/${name}`;
90-
const result = await this.run<string>(user, (api) => api.RepositoryFiles.showRaw(projectId, path, commitish));
94+
const result = await this.run<string>(user, (api) =>
95+
api.RepositoryFiles.showRaw(projectId, path, { ref: commitish }),
96+
);
9197
if (GitLab.ApiError.is(result)) {
9298
return undefined; // e.g. 404 error, because the file isn't found
9399
}
@@ -132,14 +138,17 @@ export namespace GitLab {
132138
/**
133139
* https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md#get-single-project
134140
*/
135-
export interface Project extends ProjectSchemaDefault {
141+
export interface Project extends ProjectExtendedSchema {
136142
visibility: "public" | "private" | "internal";
137143
archived: boolean;
138144
path: string; // "diaspora-project-site"
139145
path_with_namespace: string; // "diaspora/diaspora-project-site"
140146
creator_id: number;
141-
owner: User;
142-
permissions: Permissions;
147+
namespace: Pick<
148+
NamespaceSchema,
149+
"id" | "name" | "path" | "kind" | "full_path" | "avatar_url" | "web_url" | "avatar_url" | "parent_id"
150+
>;
151+
owner: Pick<UserSchema, "id" | "name" | "created_at" | "avatar_url">;
143152
merge_requests_enabled: boolean;
144153
issues_enabled: boolean;
145154
open_issues_count: number;
@@ -219,11 +228,11 @@ export namespace GitLab {
219228
merge_requests_count: number;
220229
}
221230
// https://docs.gitlab.com/ee/api/users.html#list-current-user-for-normal-users
222-
export interface User extends UserSchemaDefault {
231+
export interface User extends UserExtendedSchema {
223232
email: string;
224233
state: "active" | string;
225-
confirmed_at: string | undefined;
226-
private_profile: boolean;
234+
// confirmed_at: string | undefined;
235+
// private_profile: boolean;
227236
}
228237
export interface Permissions {
229238
project_access?: {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Copyright (c) 2022 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 { User } from "@gitpod/gitpod-protocol";
8+
import { skipIfEnvVarNotSet } from "@gitpod/gitpod-protocol/lib/util/skip-if";
9+
import { expect } from "chai";
10+
import { Container, ContainerModule } from "inversify";
11+
import { suite, retries, test, timeout } from "mocha-typescript";
12+
import { AuthProviderParams } from "../auth/auth-provider";
13+
import { DevData } from "../dev/dev-data";
14+
import { TokenProvider } from "../user/token-provider";
15+
import { GitLab, GitLabApi } from "./api";
16+
import { GitlabFileProvider } from "./file-provider";
17+
import { GitLabTokenHelper } from "./gitlab-token-helper";
18+
19+
@suite(timeout(10000), retries(2), skipIfEnvVarNotSet("GITPOD_TEST_TOKEN_GITLAB"))
20+
class TestFileProvider {
21+
static readonly AUTH_HOST_CONFIG: Partial<AuthProviderParams> = {
22+
id: "Public-GitLab",
23+
type: "GitLab",
24+
verified: true,
25+
description: "",
26+
icon: "",
27+
host: "gitlab.com",
28+
};
29+
30+
protected fileProvider: GitlabFileProvider;
31+
protected user: User;
32+
protected container: Container;
33+
34+
public before() {
35+
this.container = new Container();
36+
this.container.load(
37+
new ContainerModule((bind, unbind, isBound, rebind) => {
38+
bind(GitLabApi).toSelf().inSingletonScope();
39+
bind(AuthProviderParams).toConstantValue(TestFileProvider.AUTH_HOST_CONFIG);
40+
bind(GitLabTokenHelper).toSelf().inSingletonScope();
41+
bind(TokenProvider).toConstantValue(<TokenProvider>{
42+
getTokenForHost: async () => DevData.createGitlabTestToken(),
43+
getFreshPortAuthenticationToken: async (user: User, workspaceId: string) =>
44+
DevData.createPortAuthTestToken(workspaceId),
45+
});
46+
bind(GitlabFileProvider).toSelf().inSingletonScope();
47+
}),
48+
);
49+
this.fileProvider = this.container.get(GitlabFileProvider);
50+
this.user = DevData.createTestUser();
51+
}
52+
53+
@test public async testFileContent() {
54+
const result = await this.fileProvider.getFileContent(
55+
{
56+
repository: {
57+
owner: "AlexTugarev",
58+
name: "gp-test",
59+
host: "gitlab.com",
60+
cloneUrl: "unused in test",
61+
},
62+
revision: "af65de8b249855785bbc7a8073ebcf21f55bc8fb",
63+
},
64+
this.user,
65+
"README.md",
66+
);
67+
expect(result).to.equal(`# gp-test
68+
69+
123`);
70+
}
71+
72+
// manual test helper to create many repos
73+
@test.skip public async createManyRepos() {
74+
const api = this.container.get(GitLabApi);
75+
for (let i = 151; i < 200; i++) {
76+
try {
77+
const project = await api.run<GitLab.Project>(this.user, (g) =>
78+
g.Projects.create({
79+
name: `test_project_${i}`,
80+
path: `test_project_${i}`,
81+
namespace_id: 57982169,
82+
initialize_with_readme: true,
83+
description: "generated project to test pagination in gitpod.io/new",
84+
}),
85+
);
86+
if (GitLab.ApiError.is(project)) {
87+
console.error(`attempt ${i} error: ${JSON.stringify(project.message)}`);
88+
} else {
89+
console.log(project.name_with_namespace + " created ✅");
90+
}
91+
await new Promise((resolve) => setTimeout(resolve, 500));
92+
} catch (error) {
93+
console.error(error);
94+
}
95+
}
96+
}
97+
}
98+
99+
module.exports = new TestFileProvider();

0 commit comments

Comments
 (0)