Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include request in error cause #3309

Merged
merged 4 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ test:live:setup:
--container-image docker.io/gitlab/gitlab-ce:latest \
--container-command '/bin/sh' \
--container-arg="-c" \
--container-arg="printf '#!/usr/bin/env ruby \nu = User.first \nu.admin = true \nu.save! \nt = PersonalAccessToken.new({ user: u, name: \"gitbeaker\", scopes: [\"api\", \"read_user\"]}) \nt.set_token(\"${GITLAB_PERSONAL_ACCESS_TOKEN}\") \nt.save! \nputs t.token\n' > /opt/gitlab/embedded/service/gitlab-rails/db/fixtures/production/40_access_token.rb && /assets/wrapper" \
--container-arg="printf '#!/usr/bin/env ruby \nu = User.first \nu.admin = true \nu.save! \nt = PersonalAccessToken.new({ user: u, name: \"gitbeaker\", scopes: [\"api\", \"read_user\"]}) \nt.expires_at = 365.days.from_now \nt.set_token(ENV[\"PERSONAL_ACCESS_TOKEN\"]) \nt.save! \nputs t.token\n' > /opt/gitlab/embedded/service/gitlab-rails/db/fixtures/production/40_access_token.rb && /assets/wrapper" \
--container-env ^~^GITLAB_ROOT_PASSWORD=${GITLAB_ROOT_PASSWORD}~PERSONAL_ACCESS_TOKEN=${GITLAB_PERSONAL_ACCESS_TOKEN}~GITLAB_OMNIBUS_CONFIG="gitlab_rails['monitoring_whitelist'] = ['0.0.0.0/0', '172.17.0.1'];" \
--metadata SELF_DESTRUCT_INTERVAL_MINUTES=15 \
--metadata-from-file startup-script=./scripts/shutdown.sh \
Expand Down
23 changes: 22 additions & 1 deletion docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,25 @@ If your Gitlab server is running via HTTPS, the proper way to pass in your certi
Since everything builds off fetch support, applying a poly fill will allow for Node v16.18 instead of 18+. ie:

1. Install node-fetch
2. Set the following in your entry point: `global.fetch = require('node-fetch')`
2. Set the following in your entry point:

```js
const semver = require('semver')

if ( semver.lt(process.version, '20.0.0') ) {
global.fetch = require('node-fetch')
}
```

#### Headers / Body Timeout Error

This is caused by the internal undici fetch implementation's dispatcher [defaults](https://github.com/nodejs/undici/issues/1373) for the headers and body timeout when performing a request. In the future we will support modifying these properties in a more defined way, but for now, once can override this by setting the global symbol at the beginning of your script:

```
import { Agent } from 'undici'

globalThis[Symbol.for("undici.globalDispatcher.1")] = new Agent({
headersTimeout: 0,
bodyTimeout: 0,
});
```
3 changes: 2 additions & 1 deletion packages/requester-utils/src/RequesterUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export type ResponseBodyTypes =
| string
| string[]
| number
| void;
| void
| null;

export interface FormattedResponse<T extends ResponseBodyTypes = ResponseBodyTypes> {
body: T;
Expand Down
19 changes: 12 additions & 7 deletions packages/rest/src/Requester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
DefaultRequestOptions,
DefaultResourceOptions,
RequestOptions,
ResponseBodyTypes,
} from '@gitbeaker/requester-utils';
import {
defaultOptionsHandler as baseOptionsHandler,
Expand Down Expand Up @@ -34,7 +35,7 @@ export async function defaultOptionsHandler(
return options;
}

export async function processBody(response: Response) {
export async function processBody(response: Response): Promise<ResponseBodyTypes> {
// Split to remove potential charset info from the content type
const contentType = (response.headers.get('content-type') || '').split(';')[0].trim();

Expand All @@ -58,7 +59,7 @@ function delay(ms: number) {
async function parseResponse(response: Response, asStream = false) {
const { status, headers: rawHeaders } = response;
const headers = Object.fromEntries(rawHeaders.entries());
let body;
let body: ResponseBodyTypes | null;

if (asStream) {
body = response.body;
Expand All @@ -69,7 +70,7 @@ async function parseResponse(response: Response, asStream = false) {
return { body, headers, status };
}

async function throwFailedRequestError(response: Response) {
async function throwFailedRequestError(request: Request, response: Response) {
const content = await response.text();
const contentType = response.headers.get('Content-Type');
let description = 'API Request Error';
Expand All @@ -85,6 +86,7 @@ async function throwFailedRequestError(response: Response) {
throw new Error(response.statusText, {
cause: {
description,
request,
response,
},
});
Expand Down Expand Up @@ -112,7 +114,8 @@ export async function defaultRequestHandler(endpoint: string, options?: RequestO

/* eslint-disable no-await-in-loop */
for (let i = 0; i < maxRetries; i += 1) {
const response = await fetch(url, { ...opts, mode }).catch((e) => {
const request = new Request(url, { ...opts, mode });
const response = await fetch(request).catch((e) => {
if (e.name === 'TimeoutError' || e.name === 'AbortError') {
throw new Error('Query timeout was reached');
}
Expand All @@ -121,17 +124,19 @@ export async function defaultRequestHandler(endpoint: string, options?: RequestO
});

if (response.ok) return parseResponse(response, asStream);
if (!retryCodes.includes(response.status)) await throwFailedRequestError(response);
if (!retryCodes.includes(response.status)) await throwFailedRequestError(request, response);

// Retry
await delay(2 ** i * 0.1);
await delay(2 ** i * 0.25);

// eslint-disable-next-line
continue;
}
/* eslint-enable */

throw new Error('Could not successfully complete this request');
throw new Error(
`Could not successfully complete this request due to Error 429. Check the applicable rate limits for this endpoint.`,
);
}

export const requesterFn = createRequesterFn(defaultOptionsHandler, defaultRequestHandler);
Loading