From 620264e2c5ecb5b3eac8fab2a99b868e86e3574e Mon Sep 17 00:00:00 2001 From: Sarah French <15078782+SarahFrench@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:14:57 +0100 Subject: [PATCH] GHA to prompt reviewers to think about dependency changes when backporting (#37800) --- .../backported-dependency-change.yml | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 .github/workflows/backported-dependency-change.yml diff --git a/.github/workflows/backported-dependency-change.yml b/.github/workflows/backported-dependency-change.yml new file mode 100644 index 0000000000..09b74e47d0 --- /dev/null +++ b/.github/workflows/backported-dependency-change.yml @@ -0,0 +1,137 @@ +--- +name: Backported Dependency Changes + +on: + # The pull_request_target trigger event allows PRs raised from forks to have write permissions and access secrets. + # We uses it in this workflow to enable writing comments to the PR. + pull_request_target: + types: + - opened + - synchronize + - labeled + - unlabeled + pull_request: + types: + - opened + - synchronize + - labeled + - unlabeled + +# This workflow runs for not-yet-reviewed external contributions. +# Following a pull_request_target trigger the workflow would have write permissions, +# so we intentionally restrict the permissions to only include write access on pull-requests. +permissions: + contents: read + pull-requests: write + +jobs: + deps-change-comment: + runs-on: ubuntu-latest + steps: + - name: "Identify if go.mod files have changed" + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: changedfiles + with: + filters: | + root-go-mod: + - 'go.mod' + nested-go-mod: + - '**/*/go.mod' + list-files: json + + # This step will create or delete an existing comment; responds to changes in the PR. + - name: "Comment on PR if necessary" + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + // SETUP - values needed for function definitions below. + const commentStart = "## Backported dependency change"; + + const { number: issue_number } = context.issue; + const { owner, repo } = context.repo; + + // List all comments + const allComments = (await github.rest.issues.listComments({ + issue_number, + owner, + repo, + })).data; + const existingComment = allComments.find(c => c.body.startsWith(commentStart)); + const comment_id = existingComment?.id; + + async function createOrUpdateComment(commentDetails) { + const body = commentStart + "\n\n" + commentDetails; + let resp + if (existingComment) { + resp = await github.rest.issues.updateComment({ + owner, + repo, + comment_id, + body, + }); + } else { + resp = await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } + if (resp.status != 200){ + console.error("creating/updating comment failed, here's the response:", resp ) + core.setFailed("creating/updating comment failed with status code " + resp.status) + } + } + + async function deleteCommentIfExists() { + if (existingComment) { + const resp = await github.rest.issues.deleteComment({ + owner, + repo, + comment_id, + }); + if (resp.status >= 300 ){ + // Allow all status codes in 2XX range; deleting a non-existing comment is 204 + console.error("deleting comment failed, here's the response:", resp ) + core.setFailed("deleting comment failed with status code " + resp.status) + } + } + } + + async function getPrLabels() { + const labelsResp = await github.rest.issues.listLabelsOnIssue({ + owner, + repo, + issue_number, + }); + if (labelsResp.status != 200){ + console.error("getting the PR's labels failed, here's the response:", resp ) + core.setFailed("getting the PR's labels failed with status code " + resp.status) + } + return labelsResp + } + + // INSPECT PR & UPDATE COMMENT + + const labels = await getPrLabels() + const filteredLabels = labels.data.filter( label => { + return label.name.includes("backport") + }) + const hasBackportLabel = filteredLabels.length > 0 + const changedRootGoMod = ${{steps.changedfiles.outputs.root-go-mod}}; + const changedNestedGoMod = ${{steps.changedfiles.outputs.nested-go-mod}}; + const changesPresent = changedRootGoMod || changedNestedGoMod + if (!changesPresent){ + console.log("This PR isn't attempting to change dependencies. No comment needed.") + await deleteCommentIfExists() + } else if (!hasBackportLabel) { + console.log(`This PR contains changes to dependency-related files but doesn't have a backport label. No comment needed.` + + `\nChanged root go.mod? = ${changedRootGoMod}`+ + `\nChanged a nested go.mod? = ${changedNestedGoMod}`) + await deleteCommentIfExists() + } else { + console.log("This PR contains changes to dependency-related files and is labelled for backport. Making sure comment is present.") + const comment = "This PR makes changes to dependencies in go.mod file(s) and is labelled for backport.\n\n" + + "Notice to the maintainer: Before merging the backport of this PR please follow our security scanning processes." + await createOrUpdateComment(comment) + } \ No newline at end of file