diff --git a/.github/workflows/retract.yml b/.github/workflows/retract.yml new file mode 100644 index 0000000..655cc82 --- /dev/null +++ b/.github/workflows/retract.yml @@ -0,0 +1,42 @@ +name: Retract Release +on: + issue_comment: + types: [created] +jobs: + retract: + runs-on: ubuntu-latest + name: Retract a release + # not a pull request and has `#contains` on a line by itself + # fromJSON is used to process escape sequences + if: | + !github.event.issue.pull_request && + contains( + format(fromJSON('"\n{0}\n"'), github.event.comment.body), + fromJSON('"\n#retract\n"') + ) + steps: + - name: Get repo contents + uses: actions/checkout@v6 + with: + path: .__publish__ + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: yarn + cache-dependency-path: .__publish__/yarn.lock + + - name: Install yarn dependencies + run: yarn install --cwd ".__publish__" + + - name: Parse and set inputs + id: inputs + run: node .__publish__/src/publish/inputs.js + + - name: Comment and close + if: ${{ fromJSON(steps.inputs.outputs.result).requester == github.event.sender.login }} + env: + PUBLISH_ARGS: ${{ steps.inputs.outputs.result }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node .__publish__/src/publish/retract.js diff --git a/README.md b/README.md index 3d9e770..ba2b2c6 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ flowchart TD D --> E["Create issue in getsentry/publish"] E --> F{Release Manager Review} F -->|"Add 'accepted' label"| G[Publish workflow triggers] + F -->|"Comment '#retract'"| H[Issue closed - retracted] G --> I[Download artifacts from GitHub] I --> J["craft publish to registries"] J --> K{Publish successful?} @@ -80,6 +81,11 @@ jobs: The same `merge_target` input is also available when using the [Craft composite action](https://craft.sentry.dev/github-actions/#option-2-composite-action) directly. +## Retracting Release Request + +To retract a release request, comment `#retract` (as the only comment content) under the request you want to retract. +The only person that can do retract a release, is the same person that initially requested it and is listed in the request description. + ## Approvals Packages we release into the wider world that our customers install, require an explicit approval. This for instance applies to diff --git a/src/modules/__tests__/retract-release.js b/src/modules/__tests__/retract-release.js new file mode 100644 index 0000000..f421213 --- /dev/null +++ b/src/modules/__tests__/retract-release.js @@ -0,0 +1,49 @@ +import { vi, describe, test, expect } from "vitest"; + +vi.mock("fs"); + +const retractRelease = require("../retract-release.js"); + +describe("retract release", () => { + test("comment and close issue", async () => { + const retractArgs = { + inputs: { repo: "sentry", version: "21.3.1" }, + context: { + repo: { owner: "getsentry", repo: "publish" }, + payload: { + sender: { login: "example-user" }, + issue: { number: "211" }, + }, + }, + octokit: { + rest: { + issues: { + createComment: vi.fn(), + update: vi.fn(), + }, + }, + }, + }; + + await retractRelease(retractArgs); + + const commentMock = retractArgs.octokit.rest.issues.createComment; + expect(commentMock).toHaveBeenCalledTimes(1); + expect(commentMock.mock.calls[0][0]).toEqual({ + body: `Release request retracted by @example-user. +You may also want to remove your [release branch](https://github.com/getsentry/sentry/branches/all?query=21.3.1).`, + issue_number: "211", + owner: "getsentry", + repo: "publish", + }); + + const updateMock = retractArgs.octokit.rest.issues.update; + expect(updateMock).toHaveBeenCalledTimes(1); + expect(updateMock.mock.calls[0][0]).toEqual({ + issue_number: "211", + state: "closed", + owner: "getsentry", + repo: "publish", + }); + }); +}); diff --git a/src/modules/__tests__/update-issue.js b/src/modules/__tests__/update-issue.js index 954ef51..2031db2 100644 --- a/src/modules/__tests__/update-issue.js +++ b/src/modules/__tests__/update-issue.js @@ -148,6 +148,7 @@ Quick links: - [View changes](https://github.com/getsentry/sentry-elixir/compare/2f5876adf89822cc75199576966df4fd587f68e9...refs/heads/release/10.7.2) - [View check runs](https://github.com/getsentry/sentry-elixir/commit/fcbc69b88481a95532d10a6162a243107fabb96a/checks/) Assign the **accepted** label to this issue to approve the release. +To retract the release, the person requesting it must leave a comment containing \`#retract\` on a line by itself under this issue. ### Targets @@ -167,6 +168,7 @@ Quick links: - [View changes](https://github.com/getsentry/sentry-elixir/compare/2f5876adf89822cc75199576966df4fd587f68e9...refs/heads/release/10.7.2) - [View check runs](https://github.com/getsentry/sentry-elixir/commit/fcbc69b88481a95532d10a6162a243107fabb96a/checks/) Assign the **accepted** label to this issue to approve the release. +To retract the release, the person requesting it must leave a comment containing \`#retract\` on a line by itself under this issue. ### Targets diff --git a/src/modules/retract-release.js b/src/modules/retract-release.js new file mode 100644 index 0000000..8dd909c --- /dev/null +++ b/src/modules/retract-release.js @@ -0,0 +1,24 @@ +async function retract({ context, octokit, inputs }) { + const { repo, version } = inputs; + const { repo: publishRepo } = context; + const { number: issue_number } = context.payload.issue; + const { login } = context.payload.sender; + + await Promise.all([ + octokit.rest.issues.createComment({ + ...publishRepo, + issue_number, + body: `Release request retracted by @${login}.\nYou may also want to remove your [release branch](https://github.com/getsentry/${repo}/branches/all?query=${encodeURIComponent( + version + )}).`, + }), + + octokit.rest.issues.update({ + ...publishRepo, + issue_number, + state: "closed", + }), + ]); +}; + +module.exports = retract; \ No newline at end of file diff --git a/src/publish/retract.js b/src/publish/retract.js new file mode 100644 index 0000000..8537b51 --- /dev/null +++ b/src/publish/retract.js @@ -0,0 +1,9 @@ +const github = require('@actions/github'); +const retract = require('../modules/retract-release'); +const {getGitHubToken} = require('../libs/github'); + +const context = github.context; +const octokit = github.getOctokit(getGitHubToken()); +const inputs = JSON.parse(process.env.PUBLISH_ARGS); + +retract({context, octokit, inputs});