diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index 78ea76a020dba67cab909db0aff22e090fff1d86..a16acf51baf1d3a5177b9c9b6d3c2e04fb772604 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -61,6 +61,15 @@ The formula for the delay between attempts is `RENOVATE_X_GITLAB_MERGE_REQUEST_D Default value: `5` (attempts results in max. 13.75 seconds timeout). +## `RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS` + +If set to a positive integer, Renovate will use this as the number of attempts to check branch status before trying to add a status check. +The delay between attempts is `RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY` milliseconds. + +Default value: `2` (attempts results in maximum 2 seconds timeout). + +!!! warning Increasing this value too much penalizes projects that do not have defined pipelines, Renovate will systematically wait `RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS * RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY` milliseconds on these projects and slow down the Renovate analyzes. + ## `RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY` Adjust default time (in milliseconds) given to GitLab to create pipelines for a commit pushed by Renovate. diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts index 6eb62b886f7c406a86984f777b8c2ad80038a514..6d1176dbcbbc4e6204b29ee77c9c7f326699b29d 100644 --- a/lib/modules/platform/gitlab/index.spec.ts +++ b/lib/modules/platform/gitlab/index.spec.ts @@ -51,6 +51,7 @@ describe('modules/platform/gitlab/index', () => { token: '123test', }); delete process.env.GITLAB_IGNORE_REPO_URL; + delete process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS; delete process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY; delete process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS; delete process.env.RENOVATE_X_GITLAB_MERGE_REQUEST_DELAY; @@ -1058,6 +1059,51 @@ describe('modules/platform/gitlab/index', () => { describe('setBranchStatus', () => { const states: BranchStatus[] = ['green', 'yellow', 'red']; + it('should log message that branch commit SHA not found', async () => { + git.getBranchCommit.mockReturnValue(null); + await gitlab.setBranchStatus({ + branchName: 'some-branch', + context: 'some-context', + description: 'some-description', + state: 'green', + url: 'some-url', + }); + expect(logger.warn).toHaveBeenCalledWith( + 'Failed to get the branch commit SHA', + ); + }); + + it('should log message that failed to retrieve commit pipeline', async () => { + const scope = await initRepo(); + scope + .post( + '/api/v4/projects/some%2Frepo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .reply(200, {}) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses', + ) + .reply(200, []) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .reply(200, {}); + + timers.setTimeout.mockImplementation(() => { + throw new Error(); + }); + await gitlab.setBranchStatus({ + branchName: 'some-branch', + context: 'some-context', + description: 'some-description', + state: 'green', + url: 'some-url', + }); + expect(logger.warn).toHaveBeenCalledWith( + 'Failed to retrieve commit pipeline', + ); + }); + it.each(states)('sets branch status %s', async (state) => { const scope = await initRepo(); scope @@ -1072,7 +1118,8 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) - .reply(200, []); + .times(3) + .reply(200, {}); await expect( gitlab.setBranchStatus({ @@ -1099,7 +1146,8 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) - .reply(200, []); + .times(3) + .reply(200, {}); await gitlab.setBranchStatus({ branchName: 'some-branch', @@ -1109,7 +1157,7 @@ describe('modules/platform/gitlab/index', () => { url: 'some-url', }); - expect(timers.setTimeout.mock.calls).toHaveLength(1); + expect(timers.setTimeout.mock.calls).toHaveLength(3); expect(timers.setTimeout.mock.calls[0][0]).toBe(1000); }); @@ -1131,6 +1179,10 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) + .reply(200, {}) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) .reply(200, { last_pipeline: { id: 123 } }); await expect( @@ -1146,6 +1198,7 @@ describe('modules/platform/gitlab/index', () => { it('waits for RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY ms when set', async () => { const delay = 5000; + const retry = 2; process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY = String(delay); const scope = await initRepo(); @@ -1161,7 +1214,50 @@ describe('modules/platform/gitlab/index', () => { .get( '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', ) - .reply(200, []); + .times(3) + .reply(200, {}); + + await gitlab.setBranchStatus({ + branchName: 'some-branch', + context: 'some-context', + description: 'some-description', + state: 'green', + url: 'some-url', + }); + + expect(timers.setTimeout.mock.calls).toHaveLength(retry + 1); + expect(timers.setTimeout.mock.calls[0][0]).toBe(delay); + expect(logger.debug).toHaveBeenCalledWith( + `Pipeline not yet created. Retrying 1`, + ); + expect(logger.debug).toHaveBeenCalledWith( + `Pipeline not yet created. Retrying 2`, + ); + expect(logger.debug).toHaveBeenCalledWith( + `Pipeline not yet created after 3 attempts`, + ); + }); + + it('do RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS attemps when set', async () => { + const delay = 1000; + const retry = 5; + process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS = `${retry}`; + + const scope = await initRepo(); + scope + .post( + '/api/v4/projects/some%2Frepo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .reply(200, {}) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses', + ) + .reply(200, []) + .get( + '/api/v4/projects/some%2Frepo/repository/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e', + ) + .times(retry + 1) + .reply(200, {}); await gitlab.setBranchStatus({ branchName: 'some-branch', @@ -1171,7 +1267,7 @@ describe('modules/platform/gitlab/index', () => { url: 'some-url', }); - expect(timers.setTimeout.mock.calls).toHaveLength(1); + expect(timers.setTimeout.mock.calls).toHaveLength(retry + 1); expect(timers.setTimeout.mock.calls[0][0]).toBe(delay); }); }); diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index 64ed26afd3a3d1d289299ae431f1d02911f75791..20e5b1bb9ba134111f1e149d47348bdf3a06a720 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -994,9 +994,12 @@ export async function setBranchStatus({ }: BranchStatusConfig): Promise<void> { // First, get the branch commit SHA const branchSha = git.getBranchCommit(branchName); + if (!branchSha) { + logger.warn('Failed to get the branch commit SHA'); + return; + } // Now, check the statuses for that commit - // TODO: types (#22198) - const url = `projects/${config.repository}/statuses/${branchSha!}`; + const url = `projects/${config.repository}/statuses/${branchSha}`; let state = 'success'; if (renovateState === 'yellow') { state = 'pending'; @@ -1013,21 +1016,38 @@ export async function setBranchStatus({ options.target_url = targetUrl; } - if (branchSha) { - const commitUrl = `projects/${config.repository}/repository/commits/${branchSha}`; - await gitlabApi - .getJsonSafe(commitUrl, LastPipelineId) - .onValue((pipelineId) => { - options.pipeline_id = pipelineId; - }); - } + const retryTimes = parseInteger( + process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_CHECK_ATTEMPTS, + 2, + ); try { - // give gitlab some time to create pipelines for the sha - await setTimeout( - parseInteger(process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY, 1000), - ); + for (let attempt = 1; attempt <= retryTimes + 1; attempt += 1) { + const commitUrl = `projects/${config.repository}/repository/commits/${branchSha}`; + await gitlabApi + .getJsonSafe(commitUrl, { memCache: false }, LastPipelineId) + .onValue((pipelineId) => { + options.pipeline_id = pipelineId; + }); + if (options.pipeline_id !== undefined) { + break; + } + if (attempt >= retryTimes + 1) { + logger.debug(`Pipeline not yet created after ${attempt} attempts`); + } else { + logger.debug(`Pipeline not yet created. Retrying ${attempt}`); + } + // give gitlab some time to create pipelines for the sha + await setTimeout( + parseInteger(process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY, 1000), + ); + } + } catch (err) { + logger.debug({ err }); + logger.warn('Failed to retrieve commit pipeline'); + } + try { await gitlabApi.postJson(url, { body: options }); // update status cache