diff --git a/lib/platform/bitbucket-server/bb-got-wrapper.ts b/lib/platform/bitbucket-server/bb-got-wrapper.ts deleted file mode 100644 index 6ffbb03b7588e0..00000000000000 --- a/lib/platform/bitbucket-server/bb-got-wrapper.ts +++ /dev/null @@ -1,40 +0,0 @@ -import URL from 'url'; -import { GotJSONOptions } from 'got'; -import { PLATFORM_TYPE_BITBUCKET_SERVER } from '../../constants/platforms'; -import got from '../../util/got'; -import { GotApi, GotApiOptions, GotResponse } from '../common'; - -let baseUrl: string; - -function get( - path: string, - options: GotApiOptions & GotJSONOptions -): Promise { - const url = URL.resolve(baseUrl, path); - const opts: GotApiOptions & GotJSONOptions = { - hostType: PLATFORM_TYPE_BITBUCKET_SERVER, - json: true, - ...options, - }; - opts.headers = { - ...opts.headers, - 'X-Atlassian-Token': 'no-check', - }; - return got(url, opts); -} - -const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete']; - -export const api: GotApi = {} as any; - -for (const x of helpers) { - (api as any)[x] = (url: string, opts: any): Promise => - get(url, { ...opts, method: x.toUpperCase() }); -} - -// eslint-disable-next-line @typescript-eslint/unbound-method -api.setBaseUrl = (e: string): void => { - baseUrl = e; -}; - -export default api; diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts index 755e4b668e63e4..a65f648a038a31 100644 --- a/lib/platform/bitbucket-server/index.ts +++ b/lib/platform/bitbucket-server/index.ts @@ -11,6 +11,11 @@ import { PR_STATE_ALL, PR_STATE_OPEN } from '../../constants/pull-requests'; import { logger } from '../../logger'; import { BranchStatus } from '../../types'; import * as hostRules from '../../util/host-rules'; +import { HttpResponse } from '../../util/http'; +import { + BitbucketServerHttp, + setBaseUrl, +} from '../../util/http/bitbucket-server'; import { sanitize } from '../../util/sanitize'; import { ensureTrailingSlash } from '../../util/url'; import { @@ -22,7 +27,6 @@ import { EnsureIssueConfig, EnsureIssueResult, FindPRConfig, - GotResponse, Issue, PlatformConfig, Pr, @@ -32,7 +36,6 @@ import { } from '../common'; import GitStorage, { StatusResult } from '../git/storage'; import { smartTruncate } from '../utils/pr-body'; -import { api } from './bb-got-wrapper'; import { BbbsRestPr, BbsConfig, BbsPr, BbsRestUserRef } from './types'; import * as utils from './utils'; import { PartialDeep } from 'type-fest'; @@ -48,6 +51,8 @@ import { PartialDeep } from 'type-fest'; let config: BbsConfig = {} as any; +const bitbucketServerHttp = new BitbucketServerHttp(); + const defaults: any = { hostType: PLATFORM_TYPE_BITBUCKET_SERVER, }; @@ -74,7 +79,7 @@ export function initPlatform({ } // TODO: Add a connection check that endpoint/username/password combination are valid defaults.endpoint = ensureTrailingSlash(endpoint); - api.setBaseUrl(defaults.endpoint); + setBaseUrl(defaults.endpoint); const platformConfig: PlatformConfig = { endpoint: defaults.endpoint, }; @@ -141,7 +146,7 @@ export async function initRepo({ let renovateConfig: RenovateConfig; try { - const { body } = await api.get( + const { body } = await bitbucketServerHttp.getJson( `./rest/api/1.0/projects/${projectKey}/repos/${repositorySlug}/browse/renovate.json?limit=20000` ); if (!body.isLastPage) { @@ -190,14 +195,17 @@ export async function initRepo({ try { const info = ( - await api.get( + await bitbucketServerHttp.getJson<{ + project: { key: string }; + parent: string; + }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}` ) ).body; config.owner = info.project.key; logger.debug(`${repository} owner = ${config.owner}`); config.defaultBranch = ( - await api.get( + await bitbucketServerHttp.getJson<{ displayId: string }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/branches/default` ) ).body.displayId; @@ -222,7 +230,9 @@ export async function getRepoForceRebase(): Promise { logger.debug(`getRepoForceRebase()`); // https://docs.atlassian.com/bitbucket-server/rest/7.0.1/bitbucket-rest.html#idp342 - const res = await api.get( + const res = await bitbucketServerHttp.getJson<{ + mergeConfig: { defaultStrategy: { id: string } }; + }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/settings/pull-requests` ); @@ -233,7 +243,7 @@ export async function getRepoForceRebase(): Promise { return Boolean( res.body.mergeConfig && res.body.mergeConfig.defaultStrategy && - res.body.mergeConfig.defaultStrategy.id.indexOf('ff-only') >= 0 + res.body.mergeConfig.defaultStrategy.id.includes('ff-only') ); } @@ -281,7 +291,7 @@ export async function getPr( return null; } - const res = await api.get( + const res = await bitbucketServerHttp.getJson( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}`, { useCache: !refreshCache } ); @@ -289,16 +299,17 @@ export async function getPr( const pr: BbsPr = { displayNumber: `Pull Request #${res.body.id}`, ...utils.prInfo(res.body), - reviewers: res.body.reviewers.map( - (r: { user: { name: string } }) => r.user.name - ), + reviewers: res.body.reviewers.map((r) => r.user.name), isModified: false, }; pr.version = updatePrVersion(pr.number, pr.version); if (pr.state === PR_STATE_OPEN) { - const mergeRes = await api.get( + const mergeRes = await bitbucketServerHttp.getJson<{ + conflicted: string; + canMerge: string; + }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/merge`, { useCache: !refreshCache } ); @@ -306,7 +317,10 @@ export async function getPr( pr.canMerge = !!mergeRes.body.canMerge; const prCommits = ( - await api.get( + await bitbucketServerHttp.getJson<{ + totalCount: number; + values: { author: { emailAddress: string } }[]; + }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/commits?withCounts=true`, { useCache: !refreshCache } ) @@ -474,7 +488,7 @@ export async function deleteBranch( // getBranchPr const pr = await getBranchPr(branchName); if (pr) { - const { body } = await api.post<{ version: number }>( + const { body } = await bitbucketServerHttp.postJson<{ version: number }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${pr.number}/decline?version=${pr.version}` ); @@ -507,9 +521,12 @@ async function getStatus( const branchCommit = await config.storage.getBranchCommit(branchName); return ( - await api.get(`./rest/build-status/1.0/commits/stats/${branchCommit}`, { - useCache, - }) + await bitbucketServerHttp.getJson( + `./rest/build-status/1.0/commits/stats/${branchCommit}`, + { + useCache, + } + ) ).body; } @@ -633,7 +650,10 @@ export async function setBranchStatus({ break; } - await api.post(`./rest/build-status/1.0/commits/${branchCommit}`, { body }); + await bitbucketServerHttp.postJson( + `./rest/build-status/1.0/commits/${branchCommit}`, + { body } + ); // update status cache await getStatus(branchName, false); @@ -711,7 +731,7 @@ export async function addReviewers( const reviewersSet = new Set([...pr.reviewers, ...reviewers]); - await api.put( + await bitbucketServerHttp.putJson( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}`, { body: { @@ -765,7 +785,7 @@ async function getComments(prNo: number): Promise { async function addComment(prNo: number, text: string): Promise { // POST /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments - await api.post( + await bitbucketServerHttp.postJson( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments`, { body: { text }, @@ -779,7 +799,7 @@ async function getCommentVersion( ): Promise { // GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId} const { version } = ( - await api.get( + await bitbucketServerHttp.getJson<{ version: number }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments/${commentId}` ) ).body; @@ -795,7 +815,7 @@ async function editComment( const version = await getCommentVersion(prNo, commentId); // PUT /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId} - await api.put( + await bitbucketServerHttp.putJson( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments/${commentId}`, { body: { text, version }, @@ -807,7 +827,7 @@ async function deleteComment(prNo: number, commentId: number): Promise { const version = await getCommentVersion(prNo, commentId); // DELETE /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId} - await api.delete( + await bitbucketServerHttp.deleteJson( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments/${commentId}?version=${version}` ); } @@ -916,13 +936,13 @@ export async function createPr({ if (config.bbUseDefaultReviewers) { logger.debug(`fetching default reviewers`); const { id } = ( - await api.get<{ id: number }>( + await bitbucketServerHttp.getJson<{ id: number }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}` ) ).body; const defReviewers = ( - await api.get<{ name: string }[]>( + await bitbucketServerHttp.getJson<{ name: string }[]>( `./rest/default-reviewers/1.0/projects/${config.projectKey}/repos/${ config.repositorySlug }/reviewers?sourceRefId=refs/heads/${escapeHash( @@ -947,9 +967,9 @@ export async function createPr({ }, reviewers, }; - let prInfoRes: GotResponse; + let prInfoRes: HttpResponse; try { - prInfoRes = await api.post( + prInfoRes = await bitbucketServerHttp.postJson( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests`, { body } ); @@ -1000,7 +1020,7 @@ export async function updatePr( throw Object.assign(new Error(REPOSITORY_NOT_FOUND), { statusCode: 404 }); } - const { body } = await api.put<{ version: number }>( + const { body } = await bitbucketServerHttp.putJson<{ version: number }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}`, { body: { @@ -1037,7 +1057,7 @@ export async function mergePr( if (!pr) { throw Object.assign(new Error(REPOSITORY_NOT_FOUND), { statusCode: 404 }); } - const { body } = await api.post<{ version: number }>( + const { body } = await bitbucketServerHttp.postJson<{ version: number }>( `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/merge?version=${pr.version}` ); updatePrVersion(prNo, body.version); diff --git a/lib/platform/bitbucket-server/utils.ts b/lib/platform/bitbucket-server/utils.ts index d9982c79c2ecb9..ea9048284491be 100644 --- a/lib/platform/bitbucket-server/utils.ts +++ b/lib/platform/bitbucket-server/utils.ts @@ -5,9 +5,12 @@ import { PR_STATE_MERGED, PR_STATE_OPEN, } from '../../constants/pull-requests'; -import { api } from './bb-got-wrapper'; +import { HttpResponse } from '../../util/http'; +import { BitbucketServerHttp } from '../../util/http/bitbucket-server'; import { BbbsRestPr, BbsPr } from './types'; +const bitbucketServerHttp = new BitbucketServerHttp(); + // https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-rest.html#idp250 const prStateMapping: any = { MERGED: PR_STATE_MERGED, @@ -38,6 +41,29 @@ const addMaxLength = (inputUrl: string, limit = 100): string => { return maxedUrl; }; +async function callApi( + apiUrl: string, + method: string, + options?: any +): Promise> { + /* istanbul ignore next */ + switch (method.toLowerCase()) { + case 'post': + return bitbucketServerHttp.postJson(apiUrl, options); + case 'put': + return bitbucketServerHttp.putJson(apiUrl, options); + case 'patch': + return bitbucketServerHttp.patchJson(apiUrl, options); + case 'head': + return bitbucketServerHttp.headJson(apiUrl, options); + case 'delete': + return bitbucketServerHttp.deleteJson(apiUrl, options); + case 'get': + default: + return bitbucketServerHttp.getJson(apiUrl, options); + } +} + export async function accumulateValues( reqUrl: string, method = 'get', @@ -46,11 +72,14 @@ export async function accumulateValues( ): Promise { let accumulator: T[] = []; let nextUrl = addMaxLength(reqUrl, limit); - const lowerCaseMethod = method.toLocaleLowerCase(); while (typeof nextUrl !== 'undefined') { // TODO: fix typing - const { body } = await (api as any)[lowerCaseMethod](nextUrl, options); + const { body } = await callApi<{ + values: T[]; + isLastPage: boolean; + nextPageStart: string; + }>(nextUrl, method, options); accumulator = [...accumulator, ...body.values]; if (body.isLastPage !== false) { break; diff --git a/lib/util/http/bitbucket-server.ts b/lib/util/http/bitbucket-server.ts new file mode 100644 index 00000000000000..6c7661feff65bb --- /dev/null +++ b/lib/util/http/bitbucket-server.ts @@ -0,0 +1,30 @@ +import URL from 'url'; +import { PLATFORM_TYPE_BITBUCKET_SERVER } from '../../constants/platforms'; +import { Http, HttpOptions, HttpResponse, InternalHttpOptions } from '.'; + +let baseUrl: string; +export const setBaseUrl = (url: string): void => { + baseUrl = url; +}; + +export class BitbucketServerHttp extends Http { + constructor(options?: HttpOptions) { + super(PLATFORM_TYPE_BITBUCKET_SERVER, options); + } + + protected async request( + path: string, + options?: InternalHttpOptions + ): Promise | null> { + const url = URL.resolve(baseUrl, path); + const opts = { + baseUrl, + ...options, + }; + opts.headers = { + ...opts.headers, + 'X-Atlassian-Token': 'no-check', + }; + return super.request(url, opts); + } +}