Skip to content

Commit

Permalink
feat(gitlab): allow override mergeable check attemps and use exponent…
Browse files Browse the repository at this point in the history
…ial backoff (#26008)
  • Loading branch information
viceice committed Nov 28, 2023
1 parent 859b22e commit 8b4bfbd
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 32 deletions.
23 changes: 15 additions & 8 deletions docs/usage/self-hosted-experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ If set to any string, Renovate will use this as the `user-agent` it sends with H
If set to an integer, Renovate will use this as max page number for docker tags lookup on docker registries, instead of the default 20 pages.
This is useful for registries which ignores the `n` parameter in the query string and only return 50 tags per page.

## `RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS`

If set to an positive integer, Renovate will use this as the number of attempts to check if a merge request on GitLab is mergable before trying to automerge.
The formula for the delay between attempts is `250 * attempt * attempt` milliseconds.

Default value: `5` (attempts results in max. 13.75 seconds timeout).

## `RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY`

Adjust default time (in milliseconds) given to GitLab to create pipelines for a commit pushed by Renovate.

Can be useful for slow-running, self-hosted GitLab instances that don't react fast enough for the default delay to help.

Default value: `1000` (milliseconds).

## `RENOVATE_X_HARD_EXIT`

If set to any value, Renovate will use a "hard" `process.exit()` once all work is done, even if a sub-process is otherwise delaying Node.js from exiting.
Expand Down Expand Up @@ -129,14 +144,6 @@ If set, Renovate will rewrite GitHub Enterprise Server's pagination responses to
!!! note
For the GitHub Enterprise Server platform only.

## `RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY`

Adjust default time (in milliseconds) given to GitLab to create pipelines for a commit pushed by Renovate.

Can be useful for slow-running, self-hosted GitLab instances that don't react fast enough for the default delay to help.

Default value: `1000` (milliseconds).

## `OTEL_EXPORTER_OTLP_ENDPOINT`

If set, Renovate will export OpenTelemetry data to the supplied endpoint.
Expand Down
36 changes: 18 additions & 18 deletions lib/modules/platform/gitlab/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ describe('modules/platform/gitlab/index', () => {
});
delete process.env.GITLAB_IGNORE_REPO_URL;
delete process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY;
delete process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS;
});

async function initFakePlatform(version: string) {
Expand Down Expand Up @@ -1791,17 +1792,12 @@ describe('modules/platform/gitlab/index', () => {
.get('/api/v4/projects/undefined/merge_requests/12345')
.reply(200)
.get('/api/v4/projects/undefined/merge_requests/12345')
.reply(200, {
merge_status: 'can_be_merged',
pipeline: {
id: 29626725,
sha: '2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f',
ref: 'patch-28',
status: 'success',
},
})
.reply(200)
.get('/api/v4/projects/undefined/merge_requests/12345')
.reply(200)
.put('/api/v4/projects/undefined/merge_requests/12345/merge')
.reply(200);
process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS = '3';
expect(
await gitlab.createPr({
sourceBranch: 'some-branch',
Expand All @@ -1813,15 +1809,19 @@ describe('modules/platform/gitlab/index', () => {
usePlatformAutomerge: true,
},
}),
).toMatchInlineSnapshot(`
{
"id": 1,
"iid": 12345,
"number": 12345,
"sourceBranch": "some-branch",
"title": "some title",
}
`);
).toEqual({
id: 1,
iid: 12345,
number: 12345,
sourceBranch: 'some-branch',
title: 'some title',
});

expect(timers.setTimeout.mock.calls).toMatchObject([
[250],
[1000],
[2250],
]);
});

it('raises with squash enabled when repository squash option is default_on', async () => {
Expand Down
13 changes: 8 additions & 5 deletions lib/modules/platform/gitlab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as git from '../../../util/git';
import * as hostRules from '../../../util/host-rules';
import { setBaseUrl } from '../../../util/http/gitlab';
import type { HttpResponse } from '../../../util/http/types';
import { parseInteger } from '../../../util/number';
import * as p from '../../../util/promises';
import { regEx } from '../../../util/regex';
import { sanitize } from '../../../util/sanitize';
Expand Down Expand Up @@ -644,7 +645,11 @@ async function tryPrAutomerge(
}

const desiredStatus = 'can_be_merged';
const retryTimes = 8; // results in max. 5 min. timeout if no pipeline created
// The default value of 5 attempts results in max. 13.75 seconds timeout if no pipeline created.
const retryTimes = parseInteger(
process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS,
5,
);

// Check for correct merge request status before setting `merge_when_pipeline_succeeds` to `true`.
for (let attempt = 1; attempt <= retryTimes; attempt += 1) {
Expand All @@ -658,7 +663,7 @@ async function tryPrAutomerge(
if (body.merge_status === desiredStatus && body.pipeline !== null) {
break;
}
await setTimeout(500 * attempt);
await setTimeout(250 * attempt ** 2); // exponential backoff
}

await gitlabApi.putJson(
Expand Down Expand Up @@ -938,9 +943,7 @@ export async function setBranchStatus({
try {
// give gitlab some time to create pipelines for the sha
await setTimeout(
process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY
? parseInt(process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY, 10)
: 1000,
parseInteger(process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY, 1000),
);

await gitlabApi.postJson(url, { body: options });
Expand Down
16 changes: 15 additions & 1 deletion lib/util/number.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { coerceNumber } from './number';
import { coerceNumber, parseInteger } from './number';

describe('util/number', () => {
it.each`
Expand All @@ -9,4 +9,18 @@ describe('util/number', () => {
`('coerceNumber($val, $def) = $expected', ({ val, def, expected }) => {
expect(coerceNumber(val, def)).toBe(expected);
});

it.each`
val | def | expected
${1} | ${2} | ${2}
${undefined} | ${2} | ${2}
${undefined} | ${undefined} | ${0}
${''} | ${undefined} | ${0}
${'-1'} | ${undefined} | ${0}
${'1.1'} | ${undefined} | ${0}
${'a'} | ${undefined} | ${0}
${'5'} | ${undefined} | ${5}
`('parseInteger($val, $def) = $expected', ({ val, def, expected }) => {
expect(parseInteger(val, def)).toBe(expected);
});
});
19 changes: 19 additions & 0 deletions lib/util/number.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import is from '@sindresorhus/is';

/**
* Coerces a value to a number with optional default value.
* @param val the value to coerce
Expand All @@ -10,3 +12,20 @@ export function coerceNumber(
): number {
return val ?? def ?? 0;
}

/**
* Parses a value as a finite positive integer with optional default value.
* If no default value is provided, the default value is 0.
* @param val Value to parse as finite integer.
* @param def Optional default value.
* @returns The parsed value or the default value if the parsed value is not finite.
*/
export function parseInteger(
val: string | undefined | null,
def?: number,
): number {
// Number.parseInt returns NaN if the value is not a finite integer.
const parsed =
is.string(val) && /^\d+$/.test(val) ? Number.parseInt(val, 10) : Number.NaN;
return Number.isFinite(parsed) ? parsed : def ?? 0;
}

0 comments on commit 8b4bfbd

Please sign in to comment.