Skip to content

Commit

Permalink
GH Actions: automate release verification steps
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jrfnl committed Dec 11, 2024
1 parent 799e293 commit 9bb88c4
Showing 1 changed file with 203 additions and 0 deletions.
203 changes: 203 additions & 0 deletions .github/workflows/verify-release.yml
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 9bb88c4

Please sign in to comment.