2019-07-13 06:09:01 +00:00
|
|
|
import URL from 'url';
|
|
|
|
import parseLinkHeader from 'parse-link-header';
|
|
|
|
import pAll from 'p-all';
|
2019-03-17 06:22:18 +00:00
|
|
|
|
2019-07-13 06:09:01 +00:00
|
|
|
import got from '../../util/got';
|
|
|
|
import { maskToken } from '../../util/mask';
|
|
|
|
import { GotApi } from '../common';
|
2019-07-15 09:04:05 +00:00
|
|
|
import { logger } from '../../logger';
|
2017-08-20 07:49:39 +00:00
|
|
|
|
2019-05-27 05:39:01 +00:00
|
|
|
const hostType = 'github';
|
|
|
|
let baseUrl = 'https://api.github.com/';
|
2019-05-20 08:59:30 +00:00
|
|
|
|
2019-07-13 06:09:01 +00:00
|
|
|
async function get(
|
|
|
|
path: string,
|
|
|
|
options?: any,
|
|
|
|
okToRetry = true
|
|
|
|
): Promise<any> {
|
2018-07-06 05:26:36 +00:00
|
|
|
const opts = {
|
2019-05-27 05:39:01 +00:00
|
|
|
hostType,
|
|
|
|
baseUrl,
|
|
|
|
json: true,
|
2018-07-06 05:26:36 +00:00
|
|
|
...options,
|
|
|
|
};
|
|
|
|
const method = opts.method || 'get';
|
2018-09-12 17:58:21 +00:00
|
|
|
if (method.toLowerCase() === 'post' && path === 'graphql') {
|
|
|
|
// GitHub Enterprise uses unversioned graphql path
|
2019-05-30 14:42:43 +00:00
|
|
|
opts.baseUrl = opts.baseUrl.replace('/v3/', '/');
|
2017-11-16 21:13:54 +00:00
|
|
|
}
|
2018-09-05 12:18:31 +00:00
|
|
|
logger.trace(`${method.toUpperCase()} ${path}`);
|
2017-08-20 07:49:39 +00:00
|
|
|
try {
|
2018-09-07 04:28:07 +00:00
|
|
|
if (global.appMode) {
|
2017-10-17 06:04:58 +00:00
|
|
|
const appAccept = 'application/vnd.github.machine-man-preview+json';
|
2017-10-16 07:45:31 +00:00
|
|
|
opts.headers = Object.assign(
|
2017-10-17 06:04:58 +00:00
|
|
|
{},
|
2017-10-16 07:45:31 +00:00
|
|
|
{
|
2017-10-17 06:04:58 +00:00
|
|
|
accept: appAccept,
|
2018-08-28 15:07:00 +00:00
|
|
|
'user-agent':
|
|
|
|
process.env.RENOVATE_USER_AGENT ||
|
|
|
|
'https://github.com/renovatebot/renovate',
|
2017-10-16 07:45:31 +00:00
|
|
|
},
|
|
|
|
opts.headers
|
|
|
|
);
|
2017-10-17 06:04:58 +00:00
|
|
|
if (opts.headers.accept !== appAccept) {
|
|
|
|
opts.headers.accept = `${appAccept}, ${opts.headers.accept}`;
|
|
|
|
}
|
2017-10-16 07:45:31 +00:00
|
|
|
}
|
2019-05-30 14:42:43 +00:00
|
|
|
const res = await got(path, opts);
|
2018-04-09 04:07:05 +00:00
|
|
|
if (opts.paginate) {
|
2017-10-17 11:45:17 +00:00
|
|
|
// Check if result is paginated
|
2018-07-03 09:53:09 +00:00
|
|
|
const pageLimit = opts.pageLimit || 10;
|
2019-08-15 06:26:21 +00:00
|
|
|
const linkHeader = parseLinkHeader(res.headers.link as string);
|
2018-05-16 05:23:59 +00:00
|
|
|
if (linkHeader && linkHeader.next && linkHeader.last) {
|
|
|
|
let lastPage = +linkHeader.last.page;
|
2018-12-19 14:31:20 +00:00
|
|
|
if (!process.env.RENOVATE_PAGINATE_ALL && opts.paginate !== 'all') {
|
2018-07-03 09:53:09 +00:00
|
|
|
lastPage = Math.min(pageLimit, lastPage);
|
2018-05-16 05:23:59 +00:00
|
|
|
}
|
|
|
|
const pageNumbers = Array.from(
|
|
|
|
new Array(lastPage),
|
|
|
|
(x, i) => i + 1
|
|
|
|
).slice(1);
|
2019-03-17 06:22:18 +00:00
|
|
|
const queue = pageNumbers.map(page => () => {
|
2019-05-20 08:59:30 +00:00
|
|
|
const nextUrl = URL.parse(linkHeader.next.url, true);
|
|
|
|
delete nextUrl.search;
|
2019-07-13 06:09:01 +00:00
|
|
|
nextUrl.query.page = page.toString();
|
2019-05-20 08:59:30 +00:00
|
|
|
return get(
|
|
|
|
URL.format(nextUrl),
|
|
|
|
{ ...opts, paginate: false },
|
2019-05-27 06:28:14 +00:00
|
|
|
okToRetry
|
2019-05-20 08:59:30 +00:00
|
|
|
);
|
2019-03-17 06:22:18 +00:00
|
|
|
});
|
2019-07-13 06:09:01 +00:00
|
|
|
const pages = await pAll<{ body: any[] }>(queue, { concurrency: 5 });
|
2018-03-06 14:54:27 +00:00
|
|
|
res.body = res.body.concat(
|
2018-05-16 05:23:59 +00:00
|
|
|
...pages.filter(Boolean).map(page => page.body)
|
2018-03-06 14:54:27 +00:00
|
|
|
);
|
2017-10-17 08:12:40 +00:00
|
|
|
}
|
|
|
|
}
|
2018-09-11 09:05:35 +00:00
|
|
|
// istanbul ignore if
|
|
|
|
if (method === 'POST' && path === 'graphql') {
|
2018-09-11 13:31:59 +00:00
|
|
|
const goodResult = '{"data":{';
|
|
|
|
if (res.body.startsWith(goodResult)) {
|
2019-05-27 06:28:14 +00:00
|
|
|
if (!okToRetry) {
|
2018-09-11 13:31:59 +00:00
|
|
|
logger.info('Recovered graphql query');
|
|
|
|
}
|
2019-05-27 06:28:14 +00:00
|
|
|
} else if (okToRetry) {
|
2018-09-11 13:22:05 +00:00
|
|
|
logger.info('Retrying graphql query');
|
2018-09-11 15:44:30 +00:00
|
|
|
opts.body = opts.body.replace('first: 100', 'first: 25');
|
2019-05-27 06:28:14 +00:00
|
|
|
return get(path, opts, !okToRetry);
|
2018-09-11 13:22:05 +00:00
|
|
|
}
|
2018-09-11 09:05:35 +00:00
|
|
|
}
|
2017-08-20 07:49:39 +00:00
|
|
|
return res;
|
2019-05-27 06:29:50 +00:00
|
|
|
} catch (err) /* istanbul ignore next */ {
|
2019-06-04 02:10:22 +00:00
|
|
|
let message = err.message;
|
|
|
|
if (err.body && err.body.message) {
|
|
|
|
message = err.body.message;
|
|
|
|
}
|
2018-09-06 09:30:08 +00:00
|
|
|
if (
|
|
|
|
err.name === 'RequestError' &&
|
2019-06-06 16:57:08 +00:00
|
|
|
(err.code === 'ENOTFOUND' ||
|
|
|
|
err.code === 'ETIMEDOUT' ||
|
|
|
|
err.code === 'EAI_AGAIN')
|
2018-09-06 09:30:08 +00:00
|
|
|
) {
|
2019-05-27 06:29:50 +00:00
|
|
|
logger.info({ err }, 'GitHub failure: RequestError');
|
2018-09-03 08:58:20 +00:00
|
|
|
throw new Error('platform-failure');
|
|
|
|
}
|
2018-12-03 09:49:07 +00:00
|
|
|
if (err.name === 'ParseError') {
|
2019-05-27 06:29:50 +00:00
|
|
|
logger.info({ err }, 'GitHub failure: ParseError');
|
2018-12-03 09:49:07 +00:00
|
|
|
throw new Error('platform-failure');
|
|
|
|
}
|
2018-07-25 13:22:23 +00:00
|
|
|
if (err.statusCode >= 500 && err.statusCode < 600) {
|
2019-05-27 06:29:50 +00:00
|
|
|
logger.info({ err }, 'GitHub failure: 5xx');
|
2018-07-25 13:22:23 +00:00
|
|
|
throw new Error('platform-failure');
|
2018-07-09 09:28:33 +00:00
|
|
|
}
|
|
|
|
if (
|
2017-08-27 13:10:19 +00:00
|
|
|
err.statusCode === 403 &&
|
2019-06-04 02:10:22 +00:00
|
|
|
message.startsWith('You have triggered an abuse detection mechanism')
|
2017-08-27 13:10:19 +00:00
|
|
|
) {
|
2019-05-27 06:29:50 +00:00
|
|
|
logger.info({ err }, 'GitHub failure: abuse detection');
|
2018-10-28 13:46:52 +00:00
|
|
|
throw new Error('platform-failure');
|
2018-07-09 09:28:33 +00:00
|
|
|
}
|
2019-06-04 02:10:22 +00:00
|
|
|
if (err.statusCode === 403 && message.includes('Upgrade to GitHub Pro')) {
|
2019-05-27 06:29:50 +00:00
|
|
|
logger.debug({ path }, 'Endpoint needs paid GitHub plan');
|
2019-01-15 10:15:23 +00:00
|
|
|
throw err;
|
|
|
|
}
|
2019-06-04 02:10:22 +00:00
|
|
|
if (err.statusCode === 403 && message.includes('rate limit exceeded')) {
|
2019-05-27 06:29:50 +00:00
|
|
|
logger.info({ err }, 'GitHub failure: rate limit');
|
2018-03-22 08:26:20 +00:00
|
|
|
throw new Error('rate-limit-exceeded');
|
2018-10-16 14:40:13 +00:00
|
|
|
} else if (
|
|
|
|
err.statusCode === 403 &&
|
2019-06-04 02:10:22 +00:00
|
|
|
message.startsWith('Resource not accessible by integration')
|
2018-10-16 14:40:13 +00:00
|
|
|
) {
|
2019-05-27 06:29:50 +00:00
|
|
|
logger.info(
|
|
|
|
{ err },
|
|
|
|
'GitHub failure: Resource not accessible by integration'
|
|
|
|
);
|
2018-12-03 11:03:46 +00:00
|
|
|
throw new Error('integration-unauthorized');
|
2019-06-04 02:10:22 +00:00
|
|
|
} else if (err.statusCode === 401 && message.includes('Bad credentials')) {
|
2018-07-21 06:38:13 +00:00
|
|
|
const rateLimit = err.headers ? err.headers['x-ratelimit-limit'] : -1;
|
2018-06-19 17:18:01 +00:00
|
|
|
logger.info(
|
2018-05-16 04:01:02 +00:00
|
|
|
{
|
2018-09-04 12:51:58 +00:00
|
|
|
token: maskToken(opts.token),
|
|
|
|
err,
|
2018-05-16 04:01:02 +00:00
|
|
|
},
|
2019-05-27 06:29:50 +00:00
|
|
|
'GitHub failure: Bad credentials'
|
2018-05-16 04:01:02 +00:00
|
|
|
);
|
2018-07-23 11:11:56 +00:00
|
|
|
if (rateLimit === '60') {
|
2018-07-21 06:38:13 +00:00
|
|
|
throw new Error('platform-failure');
|
|
|
|
}
|
2018-05-16 04:01:02 +00:00
|
|
|
throw new Error('bad-credentials');
|
2019-07-17 07:44:37 +00:00
|
|
|
} else if (err.statusCode === 422) {
|
|
|
|
if (
|
|
|
|
err.body &&
|
|
|
|
err.body.errors &&
|
|
|
|
err.body.errors.find((e: any) => e.code === 'invalid')
|
|
|
|
) {
|
|
|
|
throw new Error('repository-changed');
|
|
|
|
}
|
|
|
|
throw new Error('platform-failure');
|
2017-09-16 04:39:04 +00:00
|
|
|
}
|
2017-08-20 07:49:39 +00:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];
|
|
|
|
|
|
|
|
for (const x of helpers) {
|
2019-07-13 06:09:01 +00:00
|
|
|
(get as any)[x] = (url: string, opts: any) =>
|
2017-12-14 10:47:00 +00:00
|
|
|
get(url, Object.assign({}, opts, { method: x.toUpperCase() }));
|
2017-08-20 07:49:39 +00:00
|
|
|
}
|
|
|
|
|
2019-07-13 06:09:01 +00:00
|
|
|
get.setBaseUrl = (u: string) => {
|
2019-05-27 05:39:01 +00:00
|
|
|
baseUrl = u;
|
2019-05-20 08:59:30 +00:00
|
|
|
};
|
|
|
|
|
2019-07-13 06:09:01 +00:00
|
|
|
export const api: GotApi = get as any;
|
|
|
|
export default api;
|