From 0ae0c774e9e4640af388cd5b160d322998b4ef39 Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Mon, 17 Mar 2025 22:20:05 +0100 Subject: [PATCH] Made "publish:github:pull-request" action idempotent Signed-off-by: Bogdan Nechyporenko --- .changeset/tough-clouds-live.md | 5 ++ .../package.json | 1 + .../src/actions/githubPullRequest.ts | 64 ++++++++++++++----- yarn.lock | 1 + 4 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 .changeset/tough-clouds-live.md diff --git a/.changeset/tough-clouds-live.md b/.changeset/tough-clouds-live.md new file mode 100644 index 0000000000..ff5638a7ef --- /dev/null +++ b/.changeset/tough-clouds-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Made "publish:github:pull-request" action idempotent diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 9ca633e659..824b6bcfbe 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -49,6 +49,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", + "@backstage/types": "workspace:^", "@octokit/webhooks": "^10.9.2", "libsodium-wrappers": "^0.7.11", "octokit": "^3.0.0", diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 5922c4e149..c0ff0fb3d4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -35,6 +35,7 @@ import { resolveSafeChildPath, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +import { JsonValue } from '@backstage/types'; export type Encoding = 'utf-8' | 'base64'; @@ -425,18 +426,33 @@ export const createPublishGithubPullRequestAction = ( if (targetBranchName) { createOptions.base = targetBranchName; } - const response = await client.createPullRequest(createOptions); - if (createWhenEmpty === false && !response) { + const pr = await ctx.checkpoint({ + key: `create.pr.${owner}.${repo}.${branchName}`, + fn: async () => { + const response = await client.createPullRequest(createOptions); + if (!response) { + return null; + } + + return { + base: response?.data.base, + html_url: response?.data.html_url, + number: response?.data.number, + }; + }, + }); + + if (createWhenEmpty === false && !pr) { ctx.logger.info('No changes to commit, pull request was not created'); return; } - if (!response) { + if (!pr) { throw new GithubResponseError('null response from Github'); } - const pullRequestNumber = response.data.number; + const pullRequestNumber = pr.number; if (reviewers || teamReviewers) { const pullRequest = { owner, repo, number: pullRequestNumber }; await requestReviewersOnPullRequest( @@ -445,12 +461,13 @@ export const createPublishGithubPullRequestAction = ( teamReviewers, client, ctx.logger, + ctx.checkpoint, ); } - const targetBranch = response.data.base.ref; + const targetBranch = pr.base.ref; ctx.output('targetBranchName', targetBranch); - ctx.output('remoteUrl', response.data.html_url); + ctx.output('remoteUrl', pr.html_url); ctx.output('pullRequestNumber', pullRequestNumber); } catch (e) { throw new GithubResponseError('Pull request creation failed', e); @@ -464,20 +481,33 @@ export const createPublishGithubPullRequestAction = ( teamReviewers: string[] | undefined, client: Octokit, logger: LoggerService, + checkpoint: (opts: { + key: string; + fn: () => Promise | T; + }) => Promise, ) { try { - const result = await client.rest.pulls.requestReviewers({ - owner: pr.owner, - repo: pr.repo, - pull_number: pr.number, - reviewers, - team_reviewers: teamReviewers ? [...new Set(teamReviewers)] : undefined, + await checkpoint({ + key: `request.reviewers.${pr.owner}.${pr.repo}.${pr.number}`, + fn: async () => { + const result = await client.rest.pulls.requestReviewers({ + owner: pr.owner, + repo: pr.repo, + pull_number: pr.number, + reviewers, + team_reviewers: teamReviewers + ? [...new Set(teamReviewers)] + : undefined, + }); + + const addedUsers = result.data.requested_reviewers?.join(', ') ?? ''; + const addedTeams = result.data.requested_teams?.join(', ') ?? ''; + + logger.info( + `Added users [${addedUsers}] and teams [${addedTeams}] as reviewers to Pull request ${pr.number}`, + ); + }, }); - const addedUsers = result.data.requested_reviewers?.join(', ') ?? ''; - const addedTeams = result.data.requested_teams?.join(', ') ?? ''; - logger.info( - `Added users [${addedUsers}] and teams [${addedTeams}] as reviewers to Pull request ${pr.number}`, - ); } catch (e) { logger.error( `Failure when adding reviewers to Pull request ${pr.number}`, diff --git a/yarn.lock b/yarn.lock index a6b0a736a0..ccdf8ec391 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7623,6 +7623,7 @@ __metadata: "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" + "@backstage/types": "workspace:^" "@octokit/webhooks": ^10.9.2 "@types/libsodium-wrappers": ^0.7.10 fs-extra: ^11.2.0