2019-02-07 19:04:23 +00:00
|
|
|
// SEE for the reference https://github.com/renovatebot/renovate/blob/c3e9e572b225085448d94aa121c7ec81c14d3955/lib/platform/bitbucket/utils.js
|
2019-05-20 13:08:18 +00:00
|
|
|
import url from 'url';
|
2022-02-11 10:02:30 +00:00
|
|
|
import is from '@sindresorhus/is';
|
2022-07-01 09:59:58 +00:00
|
|
|
import { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages';
|
|
|
|
import { logger } from '../../../logger';
|
2022-04-02 05:41:53 +00:00
|
|
|
import { HostRule, PrState } from '../../../types';
|
|
|
|
import type { GitProtocol } from '../../../types/git';
|
|
|
|
import * as git from '../../../util/git';
|
2022-03-03 09:35:26 +00:00
|
|
|
import { BitbucketServerHttp } from '../../../util/http/bitbucket-server';
|
2022-02-07 06:37:17 +00:00
|
|
|
import type {
|
|
|
|
HttpOptions,
|
|
|
|
HttpPostOptions,
|
|
|
|
HttpResponse,
|
2022-03-03 09:35:26 +00:00
|
|
|
} from '../../../util/http/types';
|
2022-07-01 09:59:58 +00:00
|
|
|
import { parseUrl } from '../../../util/url';
|
2022-05-09 10:24:28 +00:00
|
|
|
import { getPrBodyStruct } from '../pr-body';
|
2022-07-01 09:59:58 +00:00
|
|
|
import type { GitUrlOption } from '../types';
|
2022-04-02 05:41:53 +00:00
|
|
|
import type { BbsPr, BbsRestPr, BbsRestRepo, BitbucketError } from './types';
|
2019-02-07 19:04:23 +00:00
|
|
|
|
2022-02-11 10:02:30 +00:00
|
|
|
export const BITBUCKET_INVALID_REVIEWERS_EXCEPTION =
|
2020-12-02 19:50:26 +00:00
|
|
|
'com.atlassian.bitbucket.pull.InvalidPullRequestReviewersException';
|
|
|
|
|
2020-06-01 13:35:12 +00:00
|
|
|
const bitbucketServerHttp = new BitbucketServerHttp();
|
|
|
|
|
2019-02-19 15:54:43 +00:00
|
|
|
// https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-rest.html#idp250
|
2019-05-20 13:08:18 +00:00
|
|
|
const prStateMapping: any = {
|
2020-08-18 10:19:19 +00:00
|
|
|
MERGED: PrState.Merged,
|
|
|
|
DECLINED: PrState.Closed,
|
|
|
|
OPEN: PrState.Open,
|
2019-02-07 19:04:23 +00:00
|
|
|
};
|
|
|
|
|
2021-02-11 10:33:57 +00:00
|
|
|
export function prInfo(pr: BbsRestPr): BbsPr {
|
2019-05-20 13:08:18 +00:00
|
|
|
return {
|
|
|
|
version: pr.version,
|
|
|
|
number: pr.id,
|
2022-05-09 10:24:28 +00:00
|
|
|
bodyStruct: getPrBodyStruct(pr.description),
|
2020-09-22 04:09:59 +00:00
|
|
|
sourceBranch: pr.fromRef.displayId,
|
2019-08-14 09:48:56 +00:00
|
|
|
targetBranch: pr.toRef.displayId,
|
2019-05-20 13:08:18 +00:00
|
|
|
title: pr.title,
|
|
|
|
state: prStateMapping[pr.state],
|
|
|
|
createdAt: pr.createdDate,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-11-26 15:13:07 +00:00
|
|
|
const addMaxLength = (inputUrl: string, limit = 100): string => {
|
2019-05-14 10:00:50 +00:00
|
|
|
const { search, ...parsedUrl } = url.parse(inputUrl, true); // eslint-disable-line @typescript-eslint/no-unused-vars
|
2019-02-07 19:04:23 +00:00
|
|
|
const maxedUrl = url.format({
|
|
|
|
...parsedUrl,
|
|
|
|
query: { ...parsedUrl.query, limit },
|
|
|
|
});
|
|
|
|
return maxedUrl;
|
|
|
|
};
|
|
|
|
|
2020-07-22 05:45:57 +00:00
|
|
|
function callApi<T>(
|
2020-06-01 13:35:12 +00:00
|
|
|
apiUrl: string,
|
|
|
|
method: string,
|
2020-08-27 07:05:31 +00:00
|
|
|
options?: HttpOptions | HttpPostOptions
|
2020-06-01 13:35:12 +00:00
|
|
|
): Promise<HttpResponse<T>> {
|
|
|
|
/* istanbul ignore next */
|
|
|
|
switch (method.toLowerCase()) {
|
|
|
|
case 'post':
|
2020-08-27 07:05:31 +00:00
|
|
|
return bitbucketServerHttp.postJson<T>(
|
|
|
|
apiUrl,
|
|
|
|
options as HttpPostOptions
|
|
|
|
);
|
2020-06-01 13:35:12 +00:00
|
|
|
case 'put':
|
2020-08-27 07:05:31 +00:00
|
|
|
return bitbucketServerHttp.putJson<T>(apiUrl, options as HttpPostOptions);
|
2020-06-01 13:35:12 +00:00
|
|
|
case 'patch':
|
2020-08-27 07:05:31 +00:00
|
|
|
return bitbucketServerHttp.patchJson<T>(
|
|
|
|
apiUrl,
|
|
|
|
options as HttpPostOptions
|
|
|
|
);
|
2020-06-01 13:35:12 +00:00
|
|
|
case 'head':
|
|
|
|
return bitbucketServerHttp.headJson<T>(apiUrl, options);
|
|
|
|
case 'delete':
|
2020-08-27 07:05:31 +00:00
|
|
|
return bitbucketServerHttp.deleteJson<T>(
|
|
|
|
apiUrl,
|
|
|
|
options as HttpPostOptions
|
|
|
|
);
|
2020-06-01 13:35:12 +00:00
|
|
|
case 'get':
|
|
|
|
default:
|
|
|
|
return bitbucketServerHttp.getJson<T>(apiUrl, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-26 15:13:07 +00:00
|
|
|
export async function accumulateValues<T = any>(
|
2019-05-20 13:08:18 +00:00
|
|
|
reqUrl: string,
|
|
|
|
method = 'get',
|
2020-08-27 07:05:31 +00:00
|
|
|
options?: HttpOptions | HttpPostOptions,
|
2019-05-20 13:08:18 +00:00
|
|
|
limit?: number
|
2019-11-26 15:13:07 +00:00
|
|
|
): Promise<T[]> {
|
|
|
|
let accumulator: T[] = [];
|
2019-02-07 19:04:23 +00:00
|
|
|
let nextUrl = addMaxLength(reqUrl, limit);
|
|
|
|
|
|
|
|
while (typeof nextUrl !== 'undefined') {
|
2021-04-26 20:19:30 +00:00
|
|
|
// TODO: fix typing (#9610)
|
2020-06-01 13:35:12 +00:00
|
|
|
const { body } = await callApi<{
|
|
|
|
values: T[];
|
|
|
|
isLastPage: boolean;
|
|
|
|
nextPageStart: string;
|
|
|
|
}>(nextUrl, method, options);
|
2019-02-07 19:04:23 +00:00
|
|
|
accumulator = [...accumulator, ...body.values];
|
2020-03-17 11:15:22 +00:00
|
|
|
if (body.isLastPage !== false) {
|
|
|
|
break;
|
|
|
|
}
|
2019-02-24 16:07:16 +00:00
|
|
|
|
2019-05-14 10:00:50 +00:00
|
|
|
const { search, ...parsedUrl } = url.parse(nextUrl, true); // eslint-disable-line @typescript-eslint/no-unused-vars
|
2019-02-24 16:07:16 +00:00
|
|
|
nextUrl = url.format({
|
|
|
|
...parsedUrl,
|
|
|
|
query: {
|
|
|
|
...parsedUrl.query,
|
|
|
|
start: body.nextPageStart,
|
|
|
|
},
|
|
|
|
});
|
2019-02-07 19:04:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return accumulator;
|
2019-05-20 13:08:18 +00:00
|
|
|
}
|
2019-12-03 17:21:40 +00:00
|
|
|
|
|
|
|
export interface BitbucketCommitStatus {
|
|
|
|
failed: number;
|
|
|
|
inProgress: number;
|
|
|
|
successful: number;
|
|
|
|
}
|
|
|
|
|
2020-03-09 04:56:12 +00:00
|
|
|
export type BitbucketBranchState =
|
|
|
|
| 'SUCCESSFUL'
|
|
|
|
| 'FAILED'
|
|
|
|
| 'INPROGRESS'
|
|
|
|
| 'STOPPED';
|
|
|
|
|
2019-12-03 17:21:40 +00:00
|
|
|
export interface BitbucketStatus {
|
|
|
|
key: string;
|
|
|
|
state: BitbucketBranchState;
|
|
|
|
}
|
2020-12-02 19:50:26 +00:00
|
|
|
|
|
|
|
export function isInvalidReviewersResponse(err: BitbucketError): boolean {
|
2022-02-11 10:02:30 +00:00
|
|
|
const errors = err?.response?.body?.errors ?? [];
|
2020-12-02 19:50:26 +00:00
|
|
|
return (
|
|
|
|
errors.length > 0 &&
|
|
|
|
errors.every(
|
|
|
|
(error) => error.exceptionName === BITBUCKET_INVALID_REVIEWERS_EXCEPTION
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getInvalidReviewers(err: BitbucketError): string[] {
|
2022-02-11 10:02:30 +00:00
|
|
|
const errors = err?.response?.body?.errors ?? [];
|
|
|
|
let invalidReviewers: string[] = [];
|
2020-12-02 19:50:26 +00:00
|
|
|
for (const error of errors) {
|
|
|
|
if (error.exceptionName === BITBUCKET_INVALID_REVIEWERS_EXCEPTION) {
|
|
|
|
invalidReviewers = invalidReviewers.concat(
|
2022-02-11 10:02:30 +00:00
|
|
|
error.reviewerErrors
|
|
|
|
?.map(({ context }) => context)
|
|
|
|
.filter(is.nonEmptyString) ?? []
|
2020-12-02 19:50:26 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return invalidReviewers;
|
|
|
|
}
|
2022-04-02 05:41:53 +00:00
|
|
|
|
2022-07-01 09:59:58 +00:00
|
|
|
function generateUrlFromEndpoint(
|
|
|
|
defaultEndpoint: string,
|
|
|
|
opts: HostRule,
|
|
|
|
repository: string
|
|
|
|
): string {
|
|
|
|
const url = new URL(defaultEndpoint);
|
|
|
|
const generatedUrl = git.getUrl({
|
|
|
|
protocol: url.protocol as GitProtocol,
|
2022-07-30 14:28:31 +00:00
|
|
|
// TODO: types (#7154)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
2022-07-01 09:59:58 +00:00
|
|
|
auth: `${opts.username}:${opts.password}`,
|
|
|
|
host: `${url.host}${url.pathname}${
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
|
|
url.pathname!.endsWith('/') ? '' : /* istanbul ignore next */ '/'
|
|
|
|
}scm`,
|
|
|
|
repository,
|
|
|
|
});
|
|
|
|
logger.debug({ url: generatedUrl }, `using generated endpoint URL`);
|
|
|
|
return generatedUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
function injectAuth(url: string, opts: HostRule): string {
|
|
|
|
const repoUrl = parseUrl(url)!;
|
|
|
|
if (!repoUrl) {
|
|
|
|
logger.debug(`Invalid url: ${url}`);
|
|
|
|
throw new Error(CONFIG_GIT_URL_UNAVAILABLE);
|
|
|
|
}
|
|
|
|
// TODO: null checks (#7154)
|
|
|
|
repoUrl.username = opts.username!;
|
|
|
|
repoUrl.password = opts.password!;
|
|
|
|
return repoUrl.toString();
|
|
|
|
}
|
|
|
|
|
2022-04-02 05:41:53 +00:00
|
|
|
export function getRepoGitUrl(
|
|
|
|
repository: string,
|
|
|
|
defaultEndpoint: string,
|
2022-07-01 09:59:58 +00:00
|
|
|
gitUrl: GitUrlOption | undefined,
|
2022-04-02 05:41:53 +00:00
|
|
|
info: BbsRestRepo,
|
|
|
|
opts: HostRule
|
|
|
|
): string {
|
2022-07-01 09:59:58 +00:00
|
|
|
if (gitUrl === 'ssh') {
|
|
|
|
const sshUrl = info.links.clone?.find(({ name }) => name === 'ssh');
|
|
|
|
if (sshUrl === undefined) {
|
|
|
|
throw new Error(CONFIG_GIT_URL_UNAVAILABLE);
|
|
|
|
}
|
|
|
|
logger.debug({ url: sshUrl.href }, `using ssh URL`);
|
|
|
|
return sshUrl.href;
|
2022-04-02 05:41:53 +00:00
|
|
|
}
|
2022-07-01 09:59:58 +00:00
|
|
|
let cloneUrl = info.links.clone?.find(({ name }) => name === 'http');
|
|
|
|
if (cloneUrl) {
|
2022-04-02 05:41:53 +00:00
|
|
|
// Inject auth into the API provided URL
|
2022-07-01 09:59:58 +00:00
|
|
|
return injectAuth(cloneUrl.href, opts);
|
|
|
|
}
|
|
|
|
// Http access might be disabled, try to find ssh url in this case
|
|
|
|
cloneUrl = info.links.clone?.find(({ name }) => name === 'ssh');
|
|
|
|
if (gitUrl === 'endpoint' || !cloneUrl) {
|
|
|
|
return generateUrlFromEndpoint(defaultEndpoint, opts, repository);
|
2022-04-02 05:41:53 +00:00
|
|
|
}
|
2022-07-01 09:59:58 +00:00
|
|
|
// SSH urls can be used directly
|
|
|
|
return cloneUrl.href;
|
2022-04-02 05:41:53 +00:00
|
|
|
}
|