diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8b24126..cc3e94b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,6 +25,8 @@ env: HOMEBREW_TAP_ENABLED: "false" HOMEBREW_TAP_NAME: "eschaar/vstack" HOMEBREW_TAP_REPOSITORY: "eschaar/homebrew-vstack" + HOMEBREW_TAP_REPOSITORY_OWNER: "eschaar" + HOMEBREW_TAP_REPOSITORY_NAME: "homebrew-vstack" HOMEBREW_FORMULA_NAME: "vstack" HOMEBREW_FULLY_QUALIFIED_FORMULA: "eschaar/vstack/vstack" @@ -149,11 +151,14 @@ jobs: publish-homebrew: name: Update Homebrew Tap needs: publish - if: needs.publish.result == 'success' && github.event.release.prerelease == false && env.HOMEBREW_TAP_ENABLED == 'true' + # NOTE: job-level `if` cannot use the `env` context. Toggle via repository variable + # HOMEBREW_TAP_ENABLED (value: "true" to enable). Default/absent keeps this disabled. + if: needs.publish.result == 'success' && github.event.release.prerelease == false && vars.HOMEBREW_TAP_ENABLED == 'true' runs-on: ubuntu-latest - environment: pypi + environment: homebrew env: - HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + HOMEBREW_TAP_APP_CLIENT_ID: ${{ secrets.HOMEBREW_TAP_APP_CLIENT_ID }} + HOMEBREW_TAP_APP_PRIVATE_KEY: ${{ secrets.HOMEBREW_TAP_APP_PRIVATE_KEY }} HOMEBREW_TAP_DISPATCH_SECRET: ${{ secrets.HOMEBREW_TAP_DISPATCH_SECRET }} steps: @@ -181,11 +186,30 @@ jobs: - name: Verify Homebrew tap dispatch configuration shell: bash run: | - if [[ -z "$HOMEBREW_TAP_TOKEN" ]]; then - echo "ERROR: HOMEBREW_TAP_TOKEN is not configured in the pypi environment." + if [[ -z "$HOMEBREW_TAP_APP_CLIENT_ID" ]]; then + echo "ERROR: HOMEBREW_TAP_APP_CLIENT_ID is not configured in the pypi environment." exit 1 fi + if [[ -z "$HOMEBREW_TAP_APP_PRIVATE_KEY" ]]; then + echo "ERROR: HOMEBREW_TAP_APP_PRIVATE_KEY is not configured in the pypi environment." + exit 1 + fi + + if [[ -z "$HOMEBREW_TAP_DISPATCH_SECRET" ]]; then + echo "ERROR: HOMEBREW_TAP_DISPATCH_SECRET is not configured in the pypi environment." + exit 1 + fi + + - name: Create Homebrew tap app token + id: create_tap_token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 + with: + client-id: ${{ env.HOMEBREW_TAP_APP_CLIENT_ID }} + private-key: ${{ env.HOMEBREW_TAP_APP_PRIVATE_KEY }} + owner: ${{ env.HOMEBREW_TAP_REPOSITORY_OWNER }} + repositories: ${{ env.HOMEBREW_TAP_REPOSITORY_NAME }} + - name: Fetch sdist metadata and verify checksum id: verify_sdist shell: bash @@ -264,7 +288,7 @@ jobs: curl -fsS -X POST \ -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${HOMEBREW_TAP_TOKEN}" \ + -H "Authorization: Bearer ${{ steps.create_tap_token.outputs.token }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "https://api.github.com/repos/${HOMEBREW_TAP_REPOSITORY}/dispatches" \ --data-binary @dispatch-body.json diff --git a/tests/vstack/test_publish_workflow.py b/tests/vstack/test_publish_workflow.py index 8999ad8..d19d5b1 100644 --- a/tests/vstack/test_publish_workflow.py +++ b/tests/vstack/test_publish_workflow.py @@ -20,6 +20,8 @@ def test_homebrew_job_is_gated_and_sequential(self) -> None: assert env["HOMEBREW_TAP_ENABLED"] == "false" assert env["HOMEBREW_TAP_NAME"] == "eschaar/vstack" assert env["HOMEBREW_TAP_REPOSITORY"] == "eschaar/homebrew-vstack" + assert env["HOMEBREW_TAP_REPOSITORY_OWNER"] == "eschaar" + assert env["HOMEBREW_TAP_REPOSITORY_NAME"] == "homebrew-vstack" assert env["HOMEBREW_FORMULA_NAME"] == "vstack" assert env["HOMEBREW_FULLY_QUALIFIED_FORMULA"] == "eschaar/vstack/vstack" @@ -29,7 +31,7 @@ def test_homebrew_job_is_gated_and_sequential(self) -> None: job_if = homebrew_job["if"] assert "needs.publish.result == 'success'" in job_if assert "github.event.release.prerelease == false" in job_if - assert "env.HOMEBREW_TAP_ENABLED == 'true'" in job_if + assert "vars.HOMEBREW_TAP_ENABLED == 'true'" in job_if def test_homebrew_job_verifies_sdist_and_dispatches_update(self) -> None: """Homebrew publish should verify sdist checksum before repository dispatch.""" @@ -41,12 +43,31 @@ def test_homebrew_job_verifies_sdist_and_dispatches_update(self) -> None: assert "https://pypi.org/pypi/vstack/" in verify_script assert "sha256 mismatch between PyPI metadata and downloaded tarball" in verify_script + config_step = next( + step for step in steps if step["name"] == "Verify Homebrew tap dispatch configuration" + ) + config_script = config_step["run"] + assert "HOMEBREW_TAP_APP_CLIENT_ID" in config_script + assert "HOMEBREW_TAP_APP_PRIVATE_KEY" in config_script + assert "HOMEBREW_TAP_DISPATCH_SECRET" in config_script + + app_token_step = next(step for step in steps if step.get("id") == "create_tap_token") + assert ( + app_token_step["uses"] + == "actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349" + ) + assert app_token_step["with"]["client-id"] == "${{ env.HOMEBREW_TAP_APP_CLIENT_ID }}" + assert app_token_step["with"]["private-key"] == "${{ env.HOMEBREW_TAP_APP_PRIVATE_KEY }}" + assert app_token_step["with"]["owner"] == "${{ env.HOMEBREW_TAP_REPOSITORY_OWNER }}" + assert app_token_step["with"]["repositories"] == "${{ env.HOMEBREW_TAP_REPOSITORY_NAME }}" + dispatch_step = next( step for step in steps if step["name"] == "Dispatch formula update to Homebrew tap" ) dispatch_script = dispatch_step["run"] assert "/dispatches" in dispatch_script assert "dispatch-body.json" in dispatch_script + assert "steps.create_tap_token.outputs.token" in dispatch_script step_env = dispatch_step["env"] assert step_env["RELEASE_VERSION"] == "${{ github.event.release.tag_name }}"