diff --git a/components/server/src/bitbucket-server/bitbucket-api-factory.ts b/components/server/src/bitbucket-server/bitbucket-api-factory.ts index 245cae2b251d09..c577a605368883 100644 --- a/components/server/src/bitbucket-server/bitbucket-api-factory.ts +++ b/components/server/src/bitbucket-server/bitbucket-api-factory.ts @@ -5,10 +5,10 @@ */ import { User, Token } from "@gitpod/gitpod-protocol"; -import { APIClient, Bitbucket } from "bitbucket"; import { inject, injectable } from "inversify"; import { AuthProviderParams } from "../auth/auth-provider"; import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler"; +import * as BitbucketServer from "@atlassian/bitbucket-server"; @injectable() export class BitbucketServerApiFactory { @@ -20,35 +20,40 @@ export class BitbucketServerApiFactory { * Returns a Bitbucket API client for the given user. * @param user The user the API client should be created for. */ - public async create(user: User): Promise { + public async create(user: User): Promise { const token = await this.tokenHelper.getTokenWithScopes(user, []); return this.createBitbucket(this.baseUrl, token); } - protected createBitbucket(baseUrl: string, token: Token): APIClient { - return new Bitbucket({ + protected createBitbucket(baseUrl: string, token: Token): BitbucketServer { + const options = { baseUrl, - auth: { - token: token.value - } - }); + }; + const client = new BitbucketServer(options); + client.authenticate({ + type: "token", + token: token.value + }) + return client; } protected get baseUrl(): string { - return `https://api.${this.config.host}/2.0`; + return `https://${this.config.host}/rest/api/1.0`; } } @injectable() export class BasicAuthBitbucketServerApiFactory extends BitbucketServerApiFactory { - protected createBitbucket(baseUrl: string, token: Token): APIClient { - - return new Bitbucket({ + protected createBitbucket(baseUrl: string, token: Token): BitbucketServer { + const options = { baseUrl, - auth: { - username: token.username!, - password: token.value - } - }); + }; + const client = new BitbucketServer(options); + client.authenticate({ + type: "basic", + username: token.username || "nobody", + password: token.value + }) + return client; } } diff --git a/components/server/src/bitbucket-server/bitbucket-server-api.ts b/components/server/src/bitbucket-server/bitbucket-server-api.ts new file mode 100644 index 00000000000000..0d6ab8e619f4e0 --- /dev/null +++ b/components/server/src/bitbucket-server/bitbucket-server-api.ts @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2022 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. + */ + + +declare namespace BitbucketServer { + + export namespace Schema { + export interface Repository { + id: number; + slug: string; + name: string; + public: boolean; + links: { + clone: { + href: string; + name: string; + }[] + } + project: Project; + } + + export interface Project { + key: string; + id: number; + name: string; + public: boolean; + } + } +} + + +// { +// "slug": "test123", +// "id": 3, +// "name": "test123", +// "hierarchyId": "670d2b6499d312c9deb8", +// "scmId": "git", +// "state": "AVAILABLE", +// "statusMessage": "Available", +// "forkable": true, +// "project": { +// "key": "JLDEC", +// "id": 2, +// "name": "jldec-project", +// "public": false, +// "type": "NORMAL", +// "links": { +// "self": ... +// } +// }, +// "public": false, +// "links": { +// "clone": [ +// ..., +// { +// "href": "https://bitbucket.gitpod-self-hosted.com/scm/jldec/test123.git", +// "name": "http" +// } +// ], +// "self": ... +// } +// } + + + +// { +// "name": "roboquat", +// "emailAddress": "roboquat@gitpod.io", +// "id": 102, +// "displayName": "Robot Kumquat", +// "active": true, +// "slug": "roboquat", +// "type": "NORMAL", +// "links": { +// "self": [ +// { +// "href": "https://bitbucket.gitpod-self-hosted.com/users/roboquat" +// } +// ] +// } +// } diff --git a/components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts index 1dee672a63ea97..04c3cf53fe613e 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Copyright (c) 2022 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. */ @@ -12,7 +12,7 @@ import fetch from "node-fetch"; import { AuthUserSetup } from "../auth/auth-provider"; import { GenericAuthProvider } from "../auth/generic-auth-provider"; import { BitbucketServerOAuthScopes } from "./bitbucket-server-oauth-scopes"; -import BitbucketServer = require("@atlassian/bitbucket-server") +import * as BitbucketServer from "@atlassian/bitbucket-server"; @injectable() export class BitbucketServerAuthProvider extends GenericAuthProvider { @@ -94,7 +94,7 @@ export class BitbucketServerAuthProvider extends GenericAuthProvider { return { authUser: { authId: `${user.id!}`, - authName: user.name!, + authName: user.slug!, primaryEmail: user.emailAddress!, name: user.displayName!, // avatarUrl: user.links!.avatar!.href // TODO diff --git a/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts b/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts index 71b12a22d2cfb4..63a0a350c927f0 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-context-parser.ts @@ -7,7 +7,7 @@ import { NavigatorContext, Repository, User, WorkspaceContext } from "@gitpod/gitpod-protocol"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; -import { Schema } from "bitbucket"; +import { Schema } from "@atlassian/bitbucket-server"; import { inject, injectable } from "inversify"; import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler"; import { NotFoundError } from "../errors"; @@ -47,34 +47,36 @@ export class BitbucketServerContextParser extends AbstractContextParser implemen return undefined; } - protected async isValidCommitHash(user: User, owner: string, repoName: string, potentialCommitHash: string) { - if (potentialCommitHash.length !== 40) { - return false; - } + // protected async isValidCommitHash(user: User, owner: string, repoName: string, potentialCommitHash: string) { + // if (potentialCommitHash.length !== 40) { + // return false; + // } + // try { + // const api = await this.api(user); + // const result = (await api.repositories.getCommit({ workspace: owner, repo_slug: repoName, commit: potentialCommitHash })); + // return result.data.hash === potentialCommitHash; + // } catch { + // return false; + // } + // } + + // protected async isTag(user: User, owner: string, repoName: string, potentialTag: string) { + // try { + // const api = await this.api(user); + // const result = (await api.repositories.getTag({ workspace: owner, repo_slug: repoName, name: potentialTag })); + // return result.data.name === potentialTag; + // } catch { + // return false; + // } + // } + + protected async handleNavigatorContext(ctx: TraceContext, user: User, host: string, owner: string, repoName: string, more: Partial = {}): Promise { + const span = TraceContext.startSpan("BitbucketServerContextParser.handleNavigatorContext", ctx); try { const api = await this.api(user); - const result = (await api.repositories.getCommit({ workspace: owner, repo_slug: repoName, commit: potentialCommitHash })); - return result.data.hash === potentialCommitHash; - } catch { - return false; - } - } - protected async isTag(user: User, owner: string, repoName: string, potentialTag: string) { - try { - const api = await this.api(user); - const result = (await api.repositories.getTag({ workspace: owner, repo_slug: repoName, name: potentialTag })); - return result.data.name === potentialTag; - } catch { - return false; - } - } + const repo = (await api.repos.getRepository({projectKey: owner, repositorySlug: repoName})).data; - protected async handleNavigatorContext(ctx: TraceContext, user: User, host: string, owner: string, repoName: string, more: Partial = {}, givenRepo?: Schema.Repository): Promise { - const span = TraceContext.startSpan("BitbucketServerContextParser.handleNavigatorContext", ctx); - try { - const api = await this.api(user); - const repo = givenRepo || (await api.repositories.get({ workspace: owner, repo_slug: repoName })).data; const repository = await this.toRepository(user, host, repo); span.log({ "request.finished": "" }); @@ -87,24 +89,24 @@ export class BitbucketServerContextParser extends AbstractContextParser implemen } more.refType = more.refType || "branch"; - if (!more.revision) { - const commits = (await api.repositories.listCommitsAt({ workspace: owner, repo_slug: repoName, revision: more.ref!, pagelen: 1 })).data; - more.revision = commits.values && commits.values.length > 0 ? commits.values[0].hash : ""; - if ((!commits.values || commits.values.length === 0) && more.ref === repository.defaultBranch) { - // empty repo - more.ref = undefined; - more.revision = ""; - more.refType = undefined; - } - } - - if (!more.path) { - more.isFile = false; - more.path = ""; - } else if (more.isFile === undefined) { - const fileMeta = (await api.repositories.readSrc({ workspace: owner, repo_slug: repoName, format: "meta", commit: more.revision!, path: more.path!, pagelen: 1 })).data; - more.isFile = (fileMeta as any).type === "commit_file"; - } + // if (!more.revision) { + // const commits = (await api.repositories.listCommitsAt({ workspace: owner, repo_slug: repoName, revision: more.ref!, pagelen: 1 })).data; + // more.revision = commits.values && commits.values.length > 0 ? commits.values[0].hash : ""; + // if ((!commits.values || commits.values.length === 0) && more.ref === repository.defaultBranch) { + // // empty repo + // more.ref = undefined; + // more.revision = ""; + // more.refType = undefined; + // } + // } + + // if (!more.path) { + // more.isFile = false; + // more.path = ""; + // } else if (more.isFile === undefined) { + // const fileMeta = (await api.repositories.readSrc({ workspace: owner, repo_slug: repoName, format: "meta", commit: more.revision!, path: more.path!, pagelen: 1 })).data; + // more.isFile = (fileMeta as any).type === "commit_file"; + // } return { ...more, @@ -125,27 +127,26 @@ export class BitbucketServerContextParser extends AbstractContextParser implemen if (!repo) { throw new Error('Unknown repository.'); } - // full_name: string - // The concatenation of the repository owner's username and the slugified name, e.g. "evzijst/interruptingcow". This is the same string used in Bitbucket URLs. - const fullName = repo.full_name!.split("/"); - const owner = fullName[0]; - const name = fullName[1]; + + const owner = repo.project.key; + const name = repo.name; + const cloneUrl = repo.links.clone.find(u => u.name === "http")?.href!; const result: Repository = { - cloneUrl: `https://${host}/${repo.full_name}.git`, + cloneUrl, host, name, owner, - private: !!repo.isPrivate, - defaultBranch: repo.mainbranch ? repo.mainbranch.name : DEFAULT_BRANCH, - } - if (!!repo.parent && !!repo.parent.full_name) { - const api = await this.api(user); - const parentRepo = (await api.repositories.get({ workspace: repo.parent!.full_name!.split("/")[0], repo_slug: repo.parent!.full_name!.split("/")[1] })).data; - result.fork = { - parent: await this.toRepository(user, host, parentRepo) - }; + private: !repo.public, + defaultBranch: DEFAULT_BRANCH, } + // if (!!repo.parent && !!repo.parent.full_name) { + // const api = await this.api(user); + // const parentRepo = (await api.repositories.get({ workspace: repo.parent!.full_name!.split("/")[0], repo_slug: repo.parent!.full_name!.split("/")[1] })).data; + // result.fork = { + // parent: await this.toRepository(user, host, parentRepo) + // }; + // } return result; } diff --git a/components/server/src/bitbucket-server/bitbucket-server-file-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-file-provider.ts index b606479605a9e9..feb0e61f631379 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-file-provider.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-file-provider.ts @@ -5,7 +5,6 @@ */ import { Commit, Repository, User } from "@gitpod/gitpod-protocol"; -import { log } from '@gitpod/gitpod-protocol/lib/util/logging'; import { inject, injectable } from 'inversify'; import { FileProvider, MaybeContent } from "../repohost/file-provider"; import { BitbucketServerApiFactory } from './bitbucket-api-factory'; @@ -17,35 +16,38 @@ export class BitbucketFileProvider implements FileProvider { @inject(BitbucketServerApiFactory) protected readonly apiFactory: BitbucketServerApiFactory; public async getGitpodFileContent(commit: Commit, user: User): Promise { - const yamlVersion1 = await Promise.all([ - this.getFileContent(commit, user, '.gitpod.yml'), - this.getFileContent(commit, user, '.gitpod') - ]); - return yamlVersion1.filter(f => !!f)[0]; + return undefined; + // const yamlVersion1 = await Promise.all([ + // this.getFileContent(commit, user, '.gitpod.yml'), + // this.getFileContent(commit, user, '.gitpod') + // ]); + // return yamlVersion1.filter(f => !!f)[0]; } public async getLastChangeRevision(repository: Repository, revisionOrBranch: string, user: User, path: string): Promise { - try { - const api = await this.apiFactory.create(user); - const fileMetaData = (await api.repositories.readSrc({ workspace: repository.owner, repo_slug: repository.name, commit: revisionOrBranch, path, format: "meta" })).data; - return (fileMetaData as any).commit.hash; - } catch (err) { - log.error({ userId: user.id }, err); - throw new Error(`Could not fetch ${path} of repository ${repository.owner}/${repository.name}: ${err}`); - } + // try { + // const api = await this.apiFactory.create(user); + // const fileMetaData = (await api.repositories.readSrc({ workspace: repository.owner, repo_slug: repository.name, commit: revisionOrBranch, path, format: "meta" })).data; + // return (fileMetaData as any).commit.hash; + // } catch (err) { + // log.error({ userId: user.id }, err); + // throw new Error(`Could not fetch ${path} of repository ${repository.owner}/${repository.name}: ${err}`); + // } + return "f00"; } public async getFileContent(commit: Commit, user: User, path: string) { - if (!commit.revision) { - return undefined; - } - - try { - const api = await this.apiFactory.create(user); - const contents = (await api.repositories.readSrc({ workspace: commit.repository.owner, repo_slug: commit.repository.name, commit: commit.revision, path })).data; - return contents as string; - } catch (err) { - log.error({ userId: user.id }, err); - } + return undefined; + // if (!commit.revision) { + // return undefined; + // } + + // try { + // const api = await this.apiFactory.create(user); + // const contents = (await api.repositories.readSrc({ workspace: commit.repository.owner, repo_slug: commit.repository.name, commit: commit.revision, path })).data; + // return contents as string; + // } catch (err) { + // log.error({ userId: user.id }, err); + // } } } diff --git a/components/server/src/bitbucket-server/bitbucket-server-language-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-language-provider.ts index 3e0c6eca3c346a..a144109737a704 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-language-provider.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-language-provider.ts @@ -8,7 +8,6 @@ import { Repository, User } from "@gitpod/gitpod-protocol"; import { inject, injectable } from 'inversify'; import { LanguagesProvider } from '../repohost/languages-provider'; import { BitbucketServerApiFactory } from './bitbucket-api-factory'; -import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; @injectable() export class BitbucketLanguagesProvider implements LanguagesProvider { @@ -16,16 +15,16 @@ export class BitbucketLanguagesProvider implements LanguagesProvider { @inject(BitbucketServerApiFactory) protected readonly apiFactory: BitbucketServerApiFactory; async getLanguages(repository: Repository, user: User): Promise { - try { - const api = await this.apiFactory.create(user); - const repo = await api.repositories.get({ workspace: repository.owner, repo_slug: repository.name }); - const language = repo.data.language; - if (language) { - return { [language]: 100.0 }; - } - } catch (e) { - log.warn({ userId: user.id }, "Could not get languages of Bitbucket repo."); - } + // try { + // const api = await this.apiFactory.create(user); + // const repo = await api.repositories.get({ workspace: repository.owner, repo_slug: repository.name }); + // const language = repo.data.language; + // if (language) { + // return { [language]: 100.0 }; + // } + // } catch (e) { + // log.warn({ userId: user.id }, "Could not get languages of Bitbucket repo."); + // } return {}; } } diff --git a/components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts b/components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts index 9ecfe03e008f9e..a57382d27449f5 100644 --- a/components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts +++ b/components/server/src/bitbucket-server/bitbucket-server-repository-provider.ts @@ -1,13 +1,11 @@ /** - * Copyright (c) 2020 Gitpod GmbH. All rights reserved. + * Copyright (c) 2022 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 { Branch, CommitInfo, Repository, User } from "@gitpod/gitpod-protocol"; import { inject, injectable } from 'inversify'; -import { URL } from "url"; -import { RepoURL } from '../repohost/repo-url'; import { RepositoryProvider } from '../repohost/repository-provider'; import { BitbucketServerApiFactory } from './bitbucket-api-factory'; @@ -17,85 +15,88 @@ export class BitbucketRepositoryProvider implements RepositoryProvider { @inject(BitbucketServerApiFactory) protected readonly apiFactory: BitbucketServerApiFactory; async getRepo(user: User, owner: string, name: string): Promise { - const api = await this.apiFactory.create(user); - const repo = (await api.repositories.get({ workspace: owner, repo_slug: name })).data; - let cloneUrl = repo.links!.clone!.find((x: any) => x.name === "https")!.href!; - if (cloneUrl) { - const url = new URL(cloneUrl); - url.username = ''; - cloneUrl = url.toString(); - } - const host = RepoURL.parseRepoUrl(cloneUrl)!.host; - const description = repo.description; - const avatarUrl = repo.owner!.links!.avatar!.href; - const webUrl = repo.links!.html!.href; - const defaultBranch = repo.mainbranch?.name; - return { host, owner, name, cloneUrl, description, avatarUrl, webUrl, defaultBranch }; + // const api = await this.apiFactory.create(user); + // const repo = (await api.repositories.get({ workspace: owner, repo_slug: name })).data; + // let cloneUrl = repo.links!.clone!.find((x: any) => x.name === "https")!.href!; + // if (cloneUrl) { + // const url = new URL(cloneUrl); + // url.username = ''; + // cloneUrl = url.toString(); + // } + // const host = RepoURL.parseRepoUrl(cloneUrl)!.host; + // const description = repo.description; + // const avatarUrl = repo.owner!.links!.avatar!.href; + // const webUrl = repo.links!.html!.href; + // const defaultBranch = repo.mainbranch?.name; + // return { host, owner, name, cloneUrl, description, avatarUrl, webUrl, defaultBranch }; + throw new Error("getRepo unimplemented"); } async getBranch(user: User, owner: string, repo: string, branchName: string): Promise { - const api = await this.apiFactory.create(user); - const response = await api.repositories.getBranch({ - workspace: owner, - repo_slug: repo, - name: branchName - }) + // const api = await this.apiFactory.create(user); + // const response = await api.repositories.getBranch({ + // workspace: owner, + // repo_slug: repo, + // name: branchName + // }) - const branch = response.data; + // const branch = response.data; - return { - htmlUrl: branch.links?.html?.href!, - name: branch.name!, - commit: { - sha: branch.target?.hash!, - author: branch.target?.author?.user?.display_name!, - authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href, - authorDate: branch.target?.date!, - commitMessage: branch.target?.message || "missing commit message", - } - }; + // return { + // htmlUrl: branch.links?.html?.href!, + // name: branch.name!, + // commit: { + // sha: branch.target?.hash!, + // author: branch.target?.author?.user?.display_name!, + // authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href, + // authorDate: branch.target?.date!, + // commitMessage: branch.target?.message || "missing commit message", + // } + // }; + throw new Error("getBranch unimplemented"); } async getBranches(user: User, owner: string, repo: string): Promise { const branches: Branch[] = []; - const api = await this.apiFactory.create(user); - const response = await api.repositories.listBranches({ - workspace: owner, - repo_slug: repo, - sort: "target.date" - }) + // const api = await this.apiFactory.create(user); + // const response = await api.repositories.listBranches({ + // workspace: owner, + // repo_slug: repo, + // sort: "target.date" + // }) - for (const branch of response.data.values!) { - branches.push({ - htmlUrl: branch.links?.html?.href!, - name: branch.name!, - commit: { - sha: branch.target?.hash!, - author: branch.target?.author?.user?.display_name!, - authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href, - authorDate: branch.target?.date!, - commitMessage: branch.target?.message || "missing commit message", - } - }); - } + // for (const branch of response.data.values!) { + // branches.push({ + // htmlUrl: branch.links?.html?.href!, + // name: branch.name!, + // commit: { + // sha: branch.target?.hash!, + // author: branch.target?.author?.user?.display_name!, + // authorAvatarUrl: branch.target?.author?.user?.links?.avatar?.href, + // authorDate: branch.target?.date!, + // commitMessage: branch.target?.message || "missing commit message", + // } + // }); + // } return branches; } async getCommitInfo(user: User, owner: string, repo: string, ref: string): Promise { - const api = await this.apiFactory.create(user); - const response = await api.commits.get({ - workspace: owner, - repo_slug: repo, - commit: ref - }) - const commit = response.data; - return { - sha: commit.hash!, - author: commit.author?.user?.display_name!, - authorDate: commit.date!, - commitMessage: commit.message || "missing commit message", - authorAvatarUrl: commit.author?.user?.links?.avatar?.href, - }; + return undefined; + // const api = await this.apiFactory.create(user); + // const response = await api.commits.get({ + // workspace: owner, + // repo_slug: repo, + // commit: ref + // }) + // const commit = response.data; + // return { + // sha: commit.hash!, + // author: commit.author?.user?.display_name!, + // authorDate: commit.date!, + // commitMessage: commit.message || "missing commit message", + // authorAvatarUrl: commit.author?.user?.links?.avatar?.href, + // }; } }