From 66e392f99e970f0925553d39e248af60b50a9bbc Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Sun, 26 Jan 2025 12:23:32 +0100 Subject: [PATCH] Making publish:gitlab:merge-request idempotent. Signed-off-by: Bogdan Nechyporenko --- .changeset/soft-readers-move.md | 5 + .../src/actions/gitlabMergeRequest.ts | 168 ++++++++++-------- 2 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 .changeset/soft-readers-move.md diff --git a/.changeset/soft-readers-move.md b/.changeset/soft-readers-move.md new file mode 100644 index 0000000000..6440f0e49f --- /dev/null +++ b/.changeset/soft-readers-move.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Making publish:gitlab:merge-request idempotent. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 607a904d0a..c9d6dbbcbe 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -21,9 +21,9 @@ import { serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; import { + CommitAction, Gitlab, RepositoryTreeSchema, - CommitAction, SimpleUserSchema, } from '@gitbeaker/rest'; import path from 'path'; @@ -242,7 +242,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' repoUrl, }); - let assigneeId = undefined; + let assigneeId: number | undefined = undefined; if (assignee !== undefined) { try { @@ -379,85 +379,105 @@ which uses additional API calls in order to detect whether to 'create', 'update' ); } } - if (actions.length) { - try { - await api.Commits.create(repoID, branchName, title, actions); - } catch (e) { - throw new InputError( - `Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${getErrorMessage( - e, - )}`, - ); - } - } - try { - let mergeRequest = await api.MergeRequests.create( - repoID, - branchName, - String(targetBranch), - title, - { - description, - removeSourceBranch: removeSourceBranch ? removeSourceBranch : false, - assigneeId, - reviewerIds, - }, - ); + await ctx.checkpoint({ + key: `commit.to.${repoID}.${branchName}`, + fn: async () => { + if (actions.length) { + try { + const commit = await api.Commits.create( + repoID, + branchName, + title, + actions, + ); + return commit.id; + } catch (e) { + throw new InputError( + `Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${getErrorMessage( + e, + )}`, + ); + } + } + return null; + }, + }); + await ctx.checkpoint({ + key: `create.mr.${repoID}.${branchName}`, + fn: async () => { + try { + let mergeRequest = await api.MergeRequests.create( + repoID, + branchName, + String(targetBranch), + title, + { + description, + removeSourceBranch: removeSourceBranch + ? removeSourceBranch + : false, + assigneeId, + reviewerIds, + }, + ); - // Because we don't know the code owners before the MR is created, we can't check the approval rules beforehand. - // Getting the approval rules beforehand is very difficult, especially, because of the inheritance rules for groups. - // Code owners take a moment to be processed and added to the approval rules after the MR is created. + // Because we don't know the code owners before the MR is created, we can't check the approval rules beforehand. + // Getting the approval rules beforehand is very difficult, especially, because of the inheritance rules for groups. + // Code owners take a moment to be processed and added to the approval rules after the MR is created. - while ( - mergeRequest.detailed_merge_status === 'preparing' || - mergeRequest.detailed_merge_status === 'approvals_syncing' || - mergeRequest.detailed_merge_status === 'checking' - ) { - mergeRequest = await api.MergeRequests.show(repoID, mergeRequest.iid); - ctx.logger.info(`${mergeRequest.detailed_merge_status}`); - } + while ( + mergeRequest.detailed_merge_status === 'preparing' || + mergeRequest.detailed_merge_status === 'approvals_syncing' || + mergeRequest.detailed_merge_status === 'checking' + ) { + mergeRequest = await api.MergeRequests.show( + repoID, + mergeRequest.iid, + ); + ctx.logger.info(`${mergeRequest.detailed_merge_status}`); + } - const approvalRules = await api.MergeRequestApprovals.allApprovalRules( - repoID, - { - mergerequestIId: mergeRequest.iid, - }, - ); + const approvalRules = + await api.MergeRequestApprovals.allApprovalRules(repoID, { + mergerequestIId: mergeRequest.iid, + }); - if (approvalRules.length !== 0) { - const eligibleApprovers = approvalRules - .filter(rule => rule.eligible_approvers !== undefined) - .map(rule => { - return rule.eligible_approvers as SimpleUserSchema[]; - }) - .flat(); + if (approvalRules.length !== 0) { + const eligibleApprovers = approvalRules + .filter(rule => rule.eligible_approvers !== undefined) + .map(rule => { + return rule.eligible_approvers as SimpleUserSchema[]; + }) + .flat(); - const eligibleUserIds = new Set([ - ...eligibleApprovers.map(user => user.id), - ...(reviewerIds ?? []), - ]); + const eligibleUserIds = new Set([ + ...eligibleApprovers.map(user => user.id), + ...(reviewerIds ?? []), + ]); - mergeRequest = await api.MergeRequests.edit( - repoID, - mergeRequest.iid, - { - reviewerIds: Array.from(eligibleUserIds), - }, - ); - } + mergeRequest = await api.MergeRequests.edit( + repoID, + mergeRequest.iid, + { + reviewerIds: Array.from(eligibleUserIds), + }, + ); + } - ctx.output('projectid', repoID); - ctx.output('targetBranchName', targetBranch); - ctx.output('projectPath', repoID); - ctx.output( - 'mergeRequestUrl', - mergeRequest.web_url ?? mergeRequest.webUrl, - ); - } catch (e) { - throw new InputError( - `Merge request creation failed. ${getErrorMessage(e)}`, - ); - } + ctx.output('projectid', repoID); + ctx.output('targetBranchName', targetBranch); + ctx.output('projectPath', repoID); + ctx.output( + 'mergeRequestUrl', + mergeRequest.web_url ?? mergeRequest.webUrl, + ); + } catch (e) { + throw new InputError( + `Merge request creation failed. ${getErrorMessage(e)}`, + ); + } + }, + }); }, }); };