From 3d8997dd0bb5e9998c632136ef570384bf2d039f Mon Sep 17 00:00:00 2001 From: Scott Gerlach Date: Wed, 22 Apr 2026 10:37:43 -0600 Subject: [PATCH] refactor: drop in-tap automation; mirror homebrew-cli dumb-tap pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release pipeline in stackhawk/hawkop now owns rendering and pushing of Formula/*.rb, mirroring how stackhawk/hawkscan's build.gradle.kts drives stackhawk/homebrew-cli via JGit. This tap becomes a passive destination — just Formula/*.rb files plus a README. Removed: - .github/workflows/update-formula.yml and test.yml — the release runs in the hawkop repo, so there's nothing for this repo's CI to gate on. - scripts/formula-template.rb — the template lives upstream at stackhawk/hawkop:brew/hawkop.rb.template. - scripts/update-formula.sh and its bats tests — the gradle task in hawkop handles rendering directly. Rewrote README to describe the maintenance model and the pinned-version install syntax (hawkop@X.Y.Z). Formula/hawkop.rb lands here when hawkop v0.6.2 (or later) releases. --- .github/workflows/test.yml | 93 -------------------- .github/workflows/update-formula.yml | 68 --------------- README.md | 55 ++++-------- scripts/formula-template.rb | 38 --------- scripts/test-update-formula.bats | 80 ----------------- scripts/update-formula.sh | 123 --------------------------- 6 files changed, 17 insertions(+), 440 deletions(-) delete mode 100644 .github/workflows/test.yml delete mode 100644 .github/workflows/update-formula.yml delete mode 100644 scripts/formula-template.rb delete mode 100755 scripts/test-update-formula.bats delete mode 100755 scripts/update-formula.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index c0f1b6c..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Test formula - -on: - pull_request: - push: - branches: [main] - -permissions: - contents: read - -jobs: - audit: - name: Audit and install (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macos-latest, ubuntu-latest] - steps: - - uses: actions/checkout@v4 - - - uses: Homebrew/actions/setup-homebrew@master - - - name: Skip if formula not generated yet - id: guard - run: | - if [ ! -f Formula/hawkop.rb ]; then - echo "formula not generated yet — skipping" - echo "skip=1" >> "$GITHUB_OUTPUT" - else - echo "skip=0" >> "$GITHUB_OUTPUT" - fi - - - name: Register checkout as local tap - if: steps.guard.outputs.skip == '0' - run: | - # Modern `brew audit` and `brew install` require a tap-qualified name, - # not a file path. Symlink the checked-out repo into the Taps dir so - # `stackhawk/hawkop/hawkop` resolves to the formula in this branch. - tap_parent="$(brew --repository)/Library/Taps/stackhawk" - mkdir -p "$tap_parent" - ln -sfn "$GITHUB_WORKSPACE" "$tap_parent/homebrew-hawkop" - - - name: Audit - if: steps.guard.outputs.skip == '0' - run: brew audit --strict --online stackhawk/hawkop/hawkop - - - name: Probe tarball availability - if: steps.guard.outputs.skip == '0' - id: probe - run: | - # Pick the URL matching this runner's platform. - case "${{ runner.os }}-${{ runner.arch }}" in - macOS-ARM64) target="aarch64-apple-darwin" ;; - macOS-X64) target="x86_64-apple-darwin" ;; - Linux-X64) target="x86_64-unknown-linux-gnu" ;; - Linux-ARM64) target="aarch64-unknown-linux-gnu" ;; - *) echo "unsupported runner: ${{ runner.os }}-${{ runner.arch }}"; exit 1 ;; - esac - # Formula has no explicit `version` field — it's derived from the URL by - # Homebrew. Extract it from the first hawkop-v- pattern we see. - version=$(grep -oE 'hawkop-v[0-9][0-9A-Za-z.-]+' Formula/hawkop.rb | head -n1 | sed 's/^hawkop-v//' | sed -E 's/-(x86_64|aarch64).*$//') - url="https://download.stackhawk.com/hawkop/cli/hawkop-v${version}-${target}.tar.gz" - code=$(curl -sSIo /dev/null -w '%{http_code}' "$url") - echo "url=$url" - echo "code=$code" - echo "installable=$([ "$code" = "200" ] && echo 1 || echo 0)" >> "$GITHUB_OUTPUT" - if [ "$code" != "200" ] && [ "$code" != "404" ]; then - echo "unexpected HTTP $code from $url" >&2 - exit 1 - fi - - - name: Install - if: steps.guard.outputs.skip == '0' && steps.probe.outputs.installable == '1' - run: brew install --verbose stackhawk/hawkop/hawkop - - - name: Test - if: steps.guard.outputs.skip == '0' && steps.probe.outputs.installable == '1' - run: brew test stackhawk/hawkop/hawkop - - lint-scripts: - name: Shellcheck + bats - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install tools - run: | - sudo apt-get update - sudo apt-get install -y shellcheck bats - - name: Shellcheck - run: shellcheck scripts/update-formula.sh - - name: Bats - run: bats scripts/test-update-formula.bats diff --git a/.github/workflows/update-formula.yml b/.github/workflows/update-formula.yml deleted file mode 100644 index 6bd9de3..0000000 --- a/.github/workflows/update-formula.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Update formula - -on: - workflow_dispatch: - inputs: - version: - description: 'hawkop version to release (e.g. 0.6.2)' - required: true - type: string - repository_dispatch: - types: [hawkop-release] - -permissions: - contents: write - -concurrency: - group: update-formula - cancel-in-progress: false - -jobs: - update: - name: Render and commit Formula/hawkop.rb - runs-on: ubuntu-latest - steps: - - name: Resolve version - id: resolve - env: - INPUT_VERSION: ${{ github.event.inputs.version }} - PAYLOAD_VERSION: ${{ github.event.client_payload.version }} - run: | - if [ -n "$INPUT_VERSION" ]; then - v="$INPUT_VERSION" - else - v="$PAYLOAD_VERSION" - fi - if [ -z "$v" ]; then - echo "error: no version supplied (inputs.version or client_payload.version)" >&2 - exit 2 - fi - # Defense in depth — reject anything that isn't a semver before we hand it to any shell. - if ! printf '%s' "$v" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$'; then - echo "error: invalid version format '$v'" >&2 - exit 2 - fi - echo "version=$v" >> "$GITHUB_OUTPUT" - - - uses: actions/checkout@v4 - - - uses: Homebrew/actions/setup-homebrew@master - - - name: Render formula - env: - HAWKOP_VERSION: ${{ steps.resolve.outputs.version }} - run: scripts/update-formula.sh --version "$HAWKOP_VERSION" - - - name: Commit and push - env: - HAWKOP_VERSION: ${{ steps.resolve.outputs.version }} - run: | - git config user.name "hawkop-bot" - git config user.email "noreply@stackhawk.com" - if [ -z "$(git status --porcelain Formula/hawkop.rb 2>/dev/null)" ]; then - echo "no changes to commit" - exit 0 - fi - git add Formula/hawkop.rb - git commit -m "hawkop: update to $HAWKOP_VERSION" - git push origin main diff --git a/README.md b/README.md index ff9335b..3220e63 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # homebrew-hawkop -Homebrew tap for [HawkOp](https://github.com/stackhawk/hawkop) — a CLI companion for the StackHawk AppSec Intelligence Platform. +Homebrew tap for [HawkOp](https://github.com/stackhawk/hawkop) — a CLI +companion for the StackHawk AppSec Intelligence Platform. ## Install @@ -9,8 +10,11 @@ brew tap stackhawk/hawkop brew install hawkop ``` -> Requires a published release. If you see `Error: No available formula`, the -> first release has not yet been cut — see [Releasing a new version](#releasing-a-new-version). +To pin a specific version: + +```sh +brew install stackhawk/hawkop/hawkop@0.6.1 +``` ## Upgrade @@ -26,40 +30,15 @@ brew uninstall hawkop brew untap stackhawk/hawkop ``` -## Releasing a new version - -The formula is auto-generated. Do **not** edit `Formula/hawkop.rb` by hand. - -### Manual release - -```sh -gh workflow run update-formula.yml \ - -R stackhawk/homebrew-hawkop \ - -f version=X.Y.Z -``` - -This triggers the `update-formula` workflow, which renders -`scripts/formula-template.rb` using SHA256 sidecars from -`download.stackhawk.com/hawkop/cli/` and commits the result to `main`. - -### Automated release - -Upstream `stackhawk/hawkop` can send a `repository_dispatch` event of type -`hawkop-release` with payload `{"version": "X.Y.Z"}` to this repo to trigger -the same workflow. - -## Troubleshooting - -**`brew install` fails with a checksum mismatch.** -The tarball was re-uploaded with different content than the formula recorded. -Re-run the update workflow for the affected version. +## How this tap is maintained -**`brew install` fails with HTTP 404.** -The formula is pinned to a version whose tarballs have not been published, -or the download bucket was rolled back. Check -`https://download.stackhawk.com/hawkop/cli/hawkop-v-.tar.gz` -with `curl -I` and coordinate with the `stackhawk/hawkop` release owner. +Do not edit `Formula/*.rb` by hand. The formulae in this repo are +regenerated by the [`stackhawk/hawkop`](https://github.com/stackhawk/hawkop) +release pipeline on every prod release — that pipeline owns the template, +hashes the published tarballs, renders `Formula/hawkop.rb` (latest) and +`Formula/hawkop@.rb` (pinned), and pushes the changes here as +the `runner` user. -**Shell renderer fails locally.** -Run `bats scripts/test-update-formula.bats` and -`shellcheck scripts/update-formula.sh` from the repo root. +If the formula needs to change (new platform, updated `test do` block, +etc.), edit `brew/hawkop.rb.template` in the upstream `hawkop` repo and +cut a release. diff --git a/scripts/formula-template.rb b/scripts/formula-template.rb deleted file mode 100644 index 7390e16..0000000 --- a/scripts/formula-template.rb +++ /dev/null @@ -1,38 +0,0 @@ -# This file is rendered by scripts/update-formula.sh to produce Formula/hawkop.rb. -# Do not edit Formula/hawkop.rb by hand. -class Hawkop < Formula - desc "CLI companion for the StackHawk AppSec Intelligence Platform" - homepage "https://www.stackhawk.com/" - license "MIT" - - on_macos do - on_intel do - url "https://download.stackhawk.com/hawkop/cli/hawkop-v${version}-x86_64-apple-darwin.tar.gz" - sha256 "${mac_x64_sha256}" - end - on_arm do - url "https://download.stackhawk.com/hawkop/cli/hawkop-v${version}-aarch64-apple-darwin.tar.gz" - sha256 "${mac_arm64_sha256}" - end - end - - on_linux do - on_intel do - url "https://download.stackhawk.com/hawkop/cli/hawkop-v${version}-x86_64-unknown-linux-gnu.tar.gz" - sha256 "${linux_x64_sha256}" - end - on_arm do - url "https://download.stackhawk.com/hawkop/cli/hawkop-v${version}-aarch64-unknown-linux-gnu.tar.gz" - sha256 "${linux_arm64_sha256}" - end - end - - def install - bin.install "hawkop" - end - - test do - assert_match version.to_s, shell_output("#{bin}/hawkop --version") - system bin/"hawkop", "--help" - end -end diff --git a/scripts/test-update-formula.bats b/scripts/test-update-formula.bats deleted file mode 100755 index 005a408..0000000 --- a/scripts/test-update-formula.bats +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bats - -setup() { - export SCRIPT="$BATS_TEST_DIRNAME/update-formula.sh" - export TMPDIR_TEST="$(mktemp -d)" - cd "$TMPDIR_TEST" - cp "$BATS_TEST_DIRNAME/formula-template.rb" . - mkdir -p scripts - cp "$BATS_TEST_DIRNAME/formula-template.rb" scripts/ -} - -teardown() { - rm -rf "$TMPDIR_TEST" -} - -@test "rejects missing --version" { - run "$SCRIPT" - [ "$status" -ne 0 ] - [[ "$output" == *"--version is required"* ]] -} - -@test "rejects invalid version format" { - run "$SCRIPT" --version "not-a-version" - [ "$status" -ne 0 ] - [[ "$output" == *"invalid version"* ]] -} - -@test "accepts semver" { - # Dry-run and offline mode so we don't hit the network - run "$SCRIPT" --version "1.2.3" --dry-run --offline - [ "$status" -eq 0 ] -} - -@test "accepts semver with prerelease suffix" { - run "$SCRIPT" --version "1.2.3-rc.1" --dry-run --offline - [ "$status" -eq 0 ] -} - -@test "extracts 64-char hex from sha256 sidecar" { - run bash -c "echo 'abc123$(printf "%.0s0" {1..58}) hawkop-v0.0.0-x86_64-apple-darwin.tar.gz' | '$SCRIPT' --parse-sha-stdin" - [ "$status" -eq 0 ] - [[ "$output" == abc123* ]] - [ ${#output} -eq 64 ] -} - -@test "rejects sidecar without 64-char hex" { - run bash -c "echo 'not-a-hash' | '$SCRIPT' --parse-sha-stdin" - [ "$status" -ne 0 ] - [[ "$output" == *"expected 64-char hex"* ]] -} - -@test "dry-run offline prints rendered formula to stdout" { - run "$SCRIPT" --version 1.2.3 --dry-run --offline - [ "$status" -eq 0 ] - [[ "$output" == *'class Hawkop < Formula'* ]] - # Version comes from the URL, not an explicit `version` field — Homebrew - # derives it from `hawkop-v-.tar.gz` to avoid `brew audit` - # flagging redundancy. - [[ "$output" == *'hawkop-v1.2.3-x86_64-apple-darwin.tar.gz'* ]] - [[ "$output" == *'hawkop-v1.2.3-aarch64-unknown-linux-gnu.tar.gz'* ]] - # No unsubstituted placeholders remain - [[ "$output" != *'${version}'* ]] - [[ "$output" != *'${mac_x64_sha256}'* ]] -} - -@test "writes Formula/hawkop.rb when not dry-run" { - run "$SCRIPT" --version 1.2.3 --offline - [ "$status" -eq 0 ] - [ -f "Formula/hawkop.rb" ] - run cat Formula/hawkop.rb - [[ "$output" == *'class Hawkop < Formula'* ]] -} - -@test "is idempotent — second run produces the same file" { - "$SCRIPT" --version 1.2.3 --offline - sum1=$(shasum -a 256 Formula/hawkop.rb | awk '{print $1}') - "$SCRIPT" --version 1.2.3 --offline - sum2=$(shasum -a 256 Formula/hawkop.rb | awk '{print $1}') - [ "$sum1" = "$sum2" ] -} diff --git a/scripts/update-formula.sh b/scripts/update-formula.sh deleted file mode 100755 index 5789719..0000000 --- a/scripts/update-formula.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/sh -# Render scripts/formula-template.rb to Formula/hawkop.rb for a given hawkop version. -# Downloads each tarball from download.stackhawk.com and computes its SHA256. -# -# Usage: -# scripts/update-formula.sh --version 0.6.2 -# scripts/update-formula.sh --version 0.6.2 --dry-run -# scripts/update-formula.sh --version 0.6.2 --offline # skip network, use zeros -set -eu - -parse_sha_from_stdin() { - sha=$(tr -d '\r' | grep -Eo '[0-9a-fA-F]{64}' | head -n1) - if [ -z "$sha" ]; then - echo "error: expected 64-char hex on stdin" >&2 - return 1 - fi - printf '%s' "$sha" -} - -if [ "${1:-}" = "--parse-sha-stdin" ]; then - parse_sha_from_stdin - exit $? -fi - -VERSION="" -DRY_RUN=0 -OFFLINE=0 - -while [ $# -gt 0 ]; do - case "$1" in - --version) - if [ -z "${2:-}" ]; then - echo "error: --version requires an argument" >&2 - exit 2 - fi - VERSION="$2"; shift 2 - ;; - --dry-run) DRY_RUN=1; shift ;; - --offline) OFFLINE=1; shift ;; - -h|--help) sed -n '2,8p' "$0"; exit 0 ;; - *) echo "unknown arg: $1" >&2; exit 2 ;; - esac -done - -if [ -z "$VERSION" ]; then - echo "error: --version is required" >&2 - exit 2 -fi - -if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$'; then - echo "error: invalid version '$VERSION' — expected semver like 1.2.3 or 1.2.3-rc.1" >&2 - exit 2 -fi - -BASE_URL="https://download.stackhawk.com/hawkop/cli" - -sha256_of_file() { - # Compute SHA256 of the given file. Prefer shasum (ships on macOS and most - # Linux distros); fall back to sha256sum. - if command -v shasum >/dev/null 2>&1; then - shasum -a 256 "$1" | awk '{print $1}' - elif command -v sha256sum >/dev/null 2>&1; then - sha256sum "$1" | awk '{print $1}' - else - echo "error: neither shasum nor sha256sum is available" >&2 - return 1 - fi -} - -fetch_sha() { - target="$1" - if [ "$OFFLINE" -eq 1 ]; then - printf '%064d' 0 - return 0 - fi - archive="hawkop-v${VERSION}-${target}.tar.gz" - tmpfile=$(mktemp) - trap 'rm -f "$tmpfile"' EXIT INT TERM - if ! curl -sSfL -o "$tmpfile" "${BASE_URL}/${archive}"; then - echo "error: failed to download ${archive} from ${BASE_URL}" >&2 - exit 1 - fi - sha=$(sha256_of_file "$tmpfile") - rm -f "$tmpfile" - trap - EXIT INT TERM - if [ -z "$sha" ] || [ "${#sha}" -ne 64 ]; then - echo "error: got unexpected hash for ${archive}: '$sha'" >&2 - exit 1 - fi - printf '%s' "$sha" -} - -MAC_X64_SHA=$(fetch_sha "x86_64-apple-darwin") -MAC_ARM64_SHA=$(fetch_sha "aarch64-apple-darwin") -LINUX_X64_SHA=$(fetch_sha "x86_64-unknown-linux-gnu") -LINUX_ARM64_SHA=$(fetch_sha "aarch64-unknown-linux-gnu") - -# --- Render template --- -# Resolve template path relative to this script. -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -TEMPLATE="${SCRIPT_DIR}/formula-template.rb" -if [ ! -f "$TEMPLATE" ]; then - echo "error: template not found at $TEMPLATE" >&2 - exit 1 -fi - -render() { - sed \ - -e "s|\${version}|${VERSION}|g" \ - -e "s|\${mac_x64_sha256}|${MAC_X64_SHA}|g" \ - -e "s|\${mac_arm64_sha256}|${MAC_ARM64_SHA}|g" \ - -e "s|\${linux_x64_sha256}|${LINUX_X64_SHA}|g" \ - -e "s|\${linux_arm64_sha256}|${LINUX_ARM64_SHA}|g" \ - "$TEMPLATE" -} - -if [ "$DRY_RUN" -eq 1 ]; then - render -else - mkdir -p Formula - render > Formula/hawkop.rb - echo "wrote Formula/hawkop.rb (version $VERSION)" >&2 -fi