Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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<VERSION>- 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
68 changes: 68 additions & 0 deletions .github/workflows/update-formula.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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
1 change: 0 additions & 1 deletion Formula/.gitkeep

This file was deleted.

41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ 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).

## Upgrade

```sh
Expand All @@ -22,3 +25,41 @@ brew upgrade hawkop
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.

**`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<VERSION>-<target>.tar.gz`
with `curl -I` and coordinate with the `stackhawk/hawkop` release owner.

**Shell renderer fails locally.**
Run `bats scripts/test-update-formula.bats` and
`shellcheck scripts/update-formula.sh` from the repo root.
38 changes: 38 additions & 0 deletions scripts/formula-template.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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
80 changes: 80 additions & 0 deletions scripts/test-update-formula.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/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<ver>-<target>.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" ]
}
Loading
Loading