From 9bb88c4069594fa005bd50d9fd7cffc6645d13c4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 8 Dec 2024 20:57:34 +0100 Subject: [PATCH 1/2] GH Actions: automate release verification steps While the actual releasing can not be fully automated (due to security concerns related to signing releases in a GHA workflow), there are a number of verification checks which are part of the release workflow, which _can_ be automated. These checks were previously done as manual spot-checks. With the new workflow, they will now be executed structurally and automatically whenever a release is published on GitHub. The checks which are automated via this workflow are as follows: * For the PHAR files which can be downloaded from the GH "releases" page, the (unversioned) PHAR files published on the GH Pages website and the versioned PHAR files published on the GH Pages website for Phive (but which can also be downloaded manually), the following checks will now be run automatically: - Is the PHAR file available and can it be downloaded ? - Is the ASC (detached signature) file available and can it be downloaded ? - Verify the PHAR file via the attestation (which was created when the PHAR was created for a tag). - Verify the PHAR file is GPG signed and the signature matches. - Verify the PHAR file is functional (simple command runs without problems). - Verify the version with which the PHAR file identifies itself is the expected version. * For Phive: - Install via Phive. This will automatically also check the PHAR file is signed and the signature matches. - Verify the Phive installed PHAR file via the attestation. - Verify the Phive installed PHAR file is functional (simple command runs without problems). - Verify the version with which the PHAR file identifies itself is the expected version. Note: these checks will only run for releases from this repo. If a fork of the repo would publish their own releases, the workflow would fail anyhow (as the releases wouldn't be published to the website, nor accessible via Phive), so may as well prevent the workflow from running altogether. --- .github/workflows/verify-release.yml | 203 +++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 .github/workflows/verify-release.yml diff --git a/.github/workflows/verify-release.yml b/.github/workflows/verify-release.yml new file mode 100644 index 0000000000..99f611526a --- /dev/null +++ b/.github/workflows/verify-release.yml @@ -0,0 +1,203 @@ +name: Verify release + +on: + # Run whenever a release is published. + release: + types: [published] + # And whenever this workflow is updated. + push: + paths: + - '.github/workflows/verify-release.yml' + pull_request: + paths: + - '.github/workflows/verify-release.yml' + # Allow manually triggering the workflow. + workflow_dispatch: + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + # The concurrency group contains the workflow name and the branch name. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ################################################################################## + # Verify the release is available in all the right places and works as expected. # + ################################################################################## + verify-available-downloads: + runs-on: ubuntu-latest + + # Only run this workflow in the context of this repo. + if: github.repository_owner == 'PHPCSStandards' + + strategy: + fail-fast: false + matrix: + download_flavour: + - "Release assets" + - "Unversioned web" + - "Versioned web" + pharfile: + - 'phpcs' + - 'phpcbf' + + name: "${{ matrix.download_flavour }}: ${{ matrix.pharfile }}" + + steps: + - name: Retrieve latest release info + uses: octokit/request-action@v2.x + id: get_latest_release + with: + route: GET /repos/PHPCSStandards/PHP_CodeSniffer/releases/latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "DEBUG: Show API request failure status" + if: ${{ failure() }} + run: "echo No release found. Request failed with status ${{ steps.get_latest_release.outputs.status }}" + + - name: Grab latest tag name from API response + id: version + run: | + echo "TAG=${{ fromJson(steps.get_latest_release.outputs.data).tag_name }}" >> "$GITHUB_OUTPUT" + + - name: "DEBUG: Show tag name found in API response" + run: "echo ${{ steps.version.outputs.TAG }}" + + - name: Set source URL and file name + id: source + shell: bash + run: | + if [[ "${{ matrix.download_flavour }}" == "Release assets" ]]; then + echo 'SRC=https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/latest/download/' >> "$GITHUB_OUTPUT" + echo "FILE=${{ matrix.pharfile }}.phar" >> "$GITHUB_OUTPUT" + elif [[ "${{ matrix.download_flavour }}" == "Unversioned web" ]]; then + echo 'SRC=https://phars.phpcodesniffer.com/' >> "$GITHUB_OUTPUT" + echo "FILE=${{ matrix.pharfile }}.phar" >> "$GITHUB_OUTPUT" + else + echo 'SRC=https://phars.phpcodesniffer.com/phars/' >> "$GITHUB_OUTPUT" + echo "FILE=${{ matrix.pharfile }}-${{ steps.version.outputs.TAG }}.phar" >> "$GITHUB_OUTPUT" + fi + + - name: Verify PHAR file is available and download + run: "wget -O ${{ steps.source.outputs.FILE }} ${{ steps.source.outputs.SRC }}${{ steps.source.outputs.FILE }}" + + - name: Verify signature file is available and download + run: "wget -O ${{ steps.source.outputs.FILE }}.asc ${{ steps.source.outputs.SRC }}${{ steps.source.outputs.FILE }}.asc" + + - name: "DEBUG: List files" + run: ls -Rlh + + - name: Verify attestation of the PHAR file + run: gh attestation verify ${{ steps.source.outputs.FILE }} -o PHPCSStandards + env: + GH_TOKEN: ${{ github.token }} + + - name: Download public key + env: + FINGERPRINT: "0x689DAD778FF08760E046228BA978220305CD5C32" + run: gpg --keyserver "hkps://keys.openpgp.org" --recv-keys "$FINGERPRINT" + + - name: Verify signature of the PHAR file + run: gpg --verify ${{ steps.source.outputs.FILE }}.asc ${{ steps.source.outputs.FILE }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 'latest' + ini-values: error_reporting=-1, display_errors=On + coverage: none + + # Note: the `.` is in the command to make it work for both PHPCS as well PHPCBF. + - name: Verify the PHAR is nominally functional + run: php ${{ steps.source.outputs.FILE }} . -e --standard=PSR12 + + - name: Grab the version + id: asset_version + env: + FILE_NAME: ${{ steps.source.outputs.FILE }} + # yamllint disable-line rule:line-length + run: echo "VERSION=$(php "$FILE_NAME" --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+(\.[0-9]+)+')" >> "$GITHUB_OUTPUT" + + - name: "DEBUG: Show grabbed version" + run: echo ${{ steps.asset_version.outputs.VERSION }} + + - name: Fail the build if the PHAR is not the correct version + if: ${{ steps.asset_version.outputs.VERSION != steps.version.outputs.TAG }} + run: exit 1 + + # ######################################### + # Verify install via PHIVE. + # ######################################### + verify-phive: + runs-on: ubuntu-latest + + # Only run this workflow in the context of this repo. + if: github.repository_owner == 'PHPCSStandards' + + strategy: + fail-fast: false + matrix: + pharfile: + - 'phpcs' + - 'phpcbf' + + name: "PHIVE: ${{ matrix.pharfile }}" + + steps: + - name: Retrieve latest release info + uses: octokit/request-action@v2.x + id: get_latest_release + with: + route: GET /repos/PHPCSStandards/PHP_CodeSniffer/releases/latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "DEBUG: Show API request failure status" + if: ${{ failure() }} + run: "echo No release found. Request failed with status ${{ steps.get_latest_release.outputs.status }}" + + - name: Grab latest tag name from API response + id: version + run: | + echo "TAG=${{ fromJson(steps.get_latest_release.outputs.data).tag_name }}" >> "$GITHUB_OUTPUT" + + - name: "DEBUG: Show tag name found in API response" + run: "echo ${{ steps.version.outputs.TAG }}" + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 'latest' + ini-values: error_reporting=-1, display_errors=On + coverage: none + tools: phive + + - name: Install + run: phive install ${{ matrix.pharfile }} --copy --trust-gpg-keys 689DAD778FF08760E046228BA978220305CD5C32 + + - name: "DEBUG: List files" + run: ls -R + + - name: Verify attestation of the PHAR file + run: gh attestation verify ./tools/${{ matrix.pharfile }} -o PHPCSStandards + env: + GH_TOKEN: ${{ github.token }} + + # Note: the `.` is in the command to make it work for both PHPCS as well PHPCBF. + - name: Verify the PHAR is nominally functional + run: php ./tools/${{ matrix.pharfile }} . -e --standard=PSR12 + + - name: Grab the version + id: asset_version + env: + FILE_NAME: ./tools/${{ matrix.pharfile }} + # yamllint disable-line rule:line-length + run: echo "VERSION=$(php "$FILE_NAME" --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+(\.[0-9]+)+')" >> "$GITHUB_OUTPUT" + + - name: "DEBUG: Show grabbed version" + run: echo ${{ steps.asset_version.outputs.VERSION }} + + - name: Fail the build if the PHAR is not the correct version + if: ${{ steps.asset_version.outputs.VERSION != steps.version.outputs.TAG }} + run: exit 1 From 224a57f7cea0bb5ead5edcb429dc725edd806cdb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 8 Dec 2024 21:11:52 +0100 Subject: [PATCH 2/2] Add release checklist I've been using and fine-tuning this release checklist over the past year. As part of the efforts to document the processes and policies for this repo, I'm now publishing the checklist for transparency and to make this knowledge transferable. Note: some of the steps which this list previously included in my local copy have now been automated via the "verify-release" GH Actions workflow. These steps have been removed from this checklist. Fixes 32 --- .github/release-checklist.md | 126 +++++++++++++++++++++++++++++++++++ .remarkrc | 3 +- 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 .github/release-checklist.md diff --git a/.github/release-checklist.md b/.github/release-checklist.md new file mode 100644 index 0000000000..a57baeb50a --- /dev/null +++ b/.github/release-checklist.md @@ -0,0 +1,126 @@ +# Release checklist + +## Before Release + +### General + +- [ ] Verify, and if necessary, update the version constraints for dependencies in the `composer.json` - PR #xxx +- [ ] Verify that any new functions have type declarations (ClassName/array/callable) whenever possible. +- [ ] Verify that the license tags all refer to the _new_ organisation and no longer to Squizlabs. (easily overlooked in new files) +- [ ] Verify that `@copyright` tags in new files use `@copyright 20xx PHPCSStandards and contributors`. + +### Wiki + +- [ ] Fetch changes and check against vandalism. +- [ ] Verify that any new `public` properties are listed on the Customizable Properties page in the Wiki. +- [ ] Verify that any new sniffs which have `public` properties are listed on the Customizable Properties page in the Wiki. +- [ ] Verify that any new CLI options are listed in the Wiki. +- [ ] Verify that any new Reports have a section in the Reports page in the Wiki. + +### Majors only + +- [ ] Move old changelog entries to `CHANGELOG_OLD.md` file. +- [ ] Verify that everything deprecated during the previous major was removed. +- [ ] Update the wiki for any references to anything deprecated/removed. +- [ ] Change `Config::STABILITY` from "dev" to "stable" for the branch for the new major. - PR #xxx + +### Prepare changelog + +- [ ] Prepare changelog for the release and submit the PR. - PR #xxx + - Based on the tickets in the milestone. + - Double-check that any issues which were closed by PRs included in the release, have the milestone set. + - Compare with auto-generated release notes to ensure nothing is missed. + - :pencil2: Remember to add a release link at the bottom! +- [ ] Prepare extra sections for the GH release notes. + - Use "New contributors" list from the auto-generated notes. + - Use the milestone to gather the stats. + - Add sponsor link. + - Remove square brackets from all ticket links or make them proper full links (as GH markdown parser doesn't parse these correctly). + - Change all contributor links to full inline links (as GH markdown parser on the Releases page doesn't parse these correctly). + ```md +--- + +### New Contributors + +The PHP_CodeSniffer project is happy to welcome the following new contributors: +@...., @.... + +### Statistics + +**Closed**: # issues +**Merged**: ## pull requests + +If you like to stay informed about releases and more, follow [@phpcs on Mastodon](https://phpc.social/@phpcs) or [@PHP_CodeSniffer on X](https://x.com/PHP_CodeSniffer). + +Please consider [funding the PHP_CodeSniffer project](https://opencollective.com/php_codesniffer). If you already do so: thank you! + ``` + +### Milestone + +- [ ] Close the milestone +- [ ] Open a new milestone for the next release +- [ ] If any open PRs/issues which were milestoned for this release did not make it into the release, update their milestone. + + +## Release + +- [ ] Merge the changelog PR. + For now, cherrypick the changelog to the 4.0 branch. +- [ ] Make sure all CI builds for `master` are green. +- [ ] Create a tag for the release & push it. +- [ ] Make sure all CI builds are green. +- [ ] Download the PHAR files from the GH Actions test build page. +- [ ] Sign the PHAR files using: + ```bash + gpg -u my@email.com --detach-sign --output phpcs.phar.asc phpcs.phar + gpg -u my@email.com --detach-sign --output phpcbf.phar.asc phpcbf.phar + gpg -u my@email.com --detach-sign --output phpcs-x.x.x.phar.asc phpcs-x.x.x.phar + gpg -u my@email.com --detach-sign --output phpcbf-x.x.x.phar.asc phpcbf-x.x.x.phar + ``` + - If, for whatever reason, the key is no longer available or has expired: + -> generate a new key following the steps here: . + -> upload the new key following the steps here: . + -> update the key information in the README x 3. + -> update the key info in the verify-release GHA workflow. +- [ ] Get the SHA of the files for the phive.xml file + ```bash + # Linux + sha256sum ./phpcs-x.x.x.phar + sha256sum ./phpcbf-x.x.x.phar + + # Windows + certutil -hashfile ./phpcs-x.x.x.phar SHA256 + certutil -hashfile ./phpcbf-x.x.x.phar SHA256 + ``` +- Update the `gh-pages` branch: + - [ ] Add the new release to the `phive.xml` file. + - [ ] Add the versioned PHAR files + keys in PHAR dir. + - [ ] Add the unversioned PHAR files + keys in root dir. + - [ ] Verify the attestations of the PHAR files. + ```bash + gh attestation verify phpcs.phar -o PHPCSStandards + gh attestation verify phpcbf.phar -o PHPCSStandards + gh attestation verify phars/phpcs-x.x.x.phar -o PHPCSStandards + gh attestation verify phars/phpcbf-x.x.x.phar -o PHPCSStandards + ``` + - [ ] Commit & push the changes. + - [ ] Verify that the website regenerated correctly and that the phars can be downloaded. +- [ ] Create a release & copy & paste the changelog to it. + - [ ] Upload the unversioned PHAR files + asc files to the release. + - [ ] Announce the release in the discussions forum by checking the checkbox at the bottom of the release page. +- [ ] Make sure all CI builds are green, including the verify-release workflow. + + +## After Release + +- [ ] Update the version number in the `Config::VERSION` class constant in the `src/Config.php` file to the _next_ (patch) version. + This can always be adjusted again later if needs be if it is decided that the next version will be a minor/major, but at least for dev + it should clearly show that this is bleeding edge/unreleased. +- [ ] Close release announcement in the "Discussions" for previous minors (leave the announcements related to the current minor open). + + +### Publicize + +- [ ] Post on Mastodon about the release (official account). +- [ ] Post on X about the release (official account). +- [ ] Post on LinkedIn (personal account). diff --git a/.remarkrc b/.remarkrc index 038f7b6287..9c6cbc7b2b 100644 --- a/.remarkrc +++ b/.remarkrc @@ -13,7 +13,8 @@ { "skipUrlPatterns": [ "^https?://github\\.com/PHPCSStandards/PHP_CodeSniffer/compare/[0-9\\.]+?\\.{3}[0-9\\.]+", - "^https?://github\\.com/[A-Za-z0-9-]+" + "^https?://github\\.com/[A-Za-z0-9-]+", + "^https?://x\\.com/PHP_CodeSniffer" ] } ],