Skip to content
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
52 changes: 47 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,26 @@ jobs:
goreleaser-check:
name: GoReleaser Check
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Run GoReleaser check
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: "~> v2"
args: check
version-file: .tool-versions
install-only: true

- name: Run GoReleaser check
run: goreleaser check

# `goreleaser check` only validates config syntax, so it cannot see that
# packaging bundled extensions and downloading them have to land
Expand All @@ -131,6 +140,39 @@ jobs:
- name: Test release scripts
run: make test-scripts

# Nothing else reads the generated cask before a release publishes it.
# Render it and check what would ship.
- name: Check generated Homebrew cask
run: make check-cask

- name: Upload generated cask
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: generated-cask
path: dist/homebrew/Casks/lstk.rb
if-no-files-found: error

# The cask cops need Homebrew, absent on the Linux runners. Lints the bytes
# the job above rendered, not a second render that could diverge. Left out of
# `release`'s `needs:` for now, like govulncheck, until it has proven itself.
cask-lint:
name: Cask Lint
runs-on: macos-latest
needs: goreleaser-check
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Download generated cask
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: generated-cask
path: dist/homebrew/Casks

- name: Run Homebrew linters
run: make lint-cask

test-unit:
name: Unit Tests
runs-on: ubuntu-latest
Expand Down Expand Up @@ -376,7 +418,7 @@ jobs:
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: "~> v2"
version-file: .tool-versions
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
golangci-lint 2.9.0
goreleaser 2.18.2
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ endif
BUILD_DIR=bin
export CGO_ENABLED=0

.PHONY: build clean test test-integration test-scripts lint govulncheck mock-generate otel
.PHONY: build clean test test-integration test-scripts check-cask lint-cask lint govulncheck mock-generate otel

# Always invoke `go build` and let Go's build cache handle incrementality; a
# file target on bin/lstk would be skipped when the binary exists, even with
Expand All @@ -28,6 +28,12 @@ test-integration: build
test-scripts:
@./scripts/bundled-extensions/test-scripts.sh

check-cask:
@./scripts/check-cask.sh

lint-cask:
@./scripts/lint-cask.sh

otel:
docker compose -f docker-compose.tracing.yaml up -d

Expand Down
63 changes: 63 additions & 0 deletions scripts/check-cask.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Render the Homebrew cask and check what a release would publish.
#
# Only a release writes the cask, into a tap with no CI, so a fault reaches
# users as their next `brew install` -- how the deprecated `postflight` stanza
# shipped (localstack/lstk#501, localstack/homebrew-tap#7).
#
# Homebrew's own linters need Homebrew, absent on the Linux runners, so they run
# against this output on macOS -- scripts/lint-cask.sh.
#
# Wipes dist/ and stubs bundled/; both paths come from the config.

set -euo pipefail

cd "$(dirname "$0")/.."

CASK=dist/homebrew/Casks/lstk.rb

if ! command -v goreleaser >/dev/null 2>&1; then
echo "goreleaser required on PATH: https://goreleaser.com/install/" >&2
exit 1
fi

# A different GoReleaser can render a different cask, so the pin is part of the
# artifact's definition.
EXPECTED=$(awk '/^goreleaser/ {print $2}' .tool-versions)
INSTALLED=$(goreleaser --version 2>/dev/null | awk '/GitVersion:/ {print $2}' | sed 's/^v//')
if [ "$INSTALLED" != "$EXPECTED" ]; then
echo "goreleaser $EXPECTED required (found: ${INSTALLED:-none})" >&2
exit 1
fi

# archives.files globs `bundled/`, so an empty tree fails the build. The cask
# ignores the bundle, and the real fetch needs a private-repo token.
scripts/bundled-extensions/fetch-bundled-extensions.sh --stub

goreleaser release --snapshot --clean

[ -f "$CASK" ] || { echo "no cask generated at $CASK" >&2; exit 1; }

fail() { echo "FAIL: $1" >&2; echo "--- $CASK ---" >&2; cat "$CASK" >&2; exit 1; }

# Homebrew warns on every `brew` operation that loads a raw-Ruby flight block.
# Only deprecated spellings match -- `_steps` leaves no trailing ` do` -- and
# indentation is ignored, so a reindent upstream cannot empty the check.
grep -Eq '^[[:space:]]*(uninstall_)?(pre|post)flight do$' "$CASK" &&
fail "cask uses a deprecated raw-Ruby flight stanza; use the *_steps form"

# Our darwin binaries are unsigned, so Gatekeeper kills them unless quarantine
# is cleared. Deleting the hook would satisfy the check above, so assert the
# step survives.
grep -q 'postflight_steps do' "$CASK" ||
fail "cask has no postflight_steps stanza"
grep -q 'com.apple.quarantine' "$CASK" ||
fail "cask no longer clears com.apple.quarantine"

# Homebrew's tokens share GoReleaser's delimiters. An unescaped
# `{{staged_path}}` fails the release; a broken escape emits a literal pointing
# somewhere else.
grep -q '"{{staged_path}}"' "$CASK" ||
fail "quarantine step does not target the whole staged dir"

echo "OK: $CASK"
46 changes: 46 additions & 0 deletions scripts/lint-cask.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Run Homebrew's own linters over a generated cask.
#
# check-cask.sh asserts what we know to look for; these cops catch what we did
# not -- `Cask/InstallSteps` is what flags localstack/lstk#501. They need
# Homebrew, absent on the Linux runners, so CI renders there and lints here.
#
# Usage: scripts/lint-cask.sh [path/to/lstk.rb]

set -euo pipefail

cd "$(dirname "$0")/.."

CASK="${1:-dist/homebrew/Casks/lstk.rb}"

[ -f "$CASK" ] || { echo "no cask at $CASK -- run 'make check-cask' first" >&2; exit 1; }

if ! command -v brew >/dev/null 2>&1; then
echo "brew required on PATH: https://brew.sh" >&2
exit 1
fi

export HOMEBREW_NO_AUTO_UPDATE=1

# `brew audit` refuses a bare path, and the cask cops only fire under `Casks/`,
# so this needs a tap. Build a throwaway one and drop it however we exit.
TAP=lstkci/caskcheck
untap() { brew untap "$TAP" >/dev/null 2>&1 || true; }
trap untap EXIT
untap
brew tap-new --no-git "$TAP" >/dev/null
TAP_CASKS="$(brew --repository "$TAP")/Casks"
mkdir -p "$TAP_CASKS"
cp "$CASK" "$TAP_CASKS/lstk.rb"

# TODO(#512): drop --except-cops once hooks.post.install_steps replaces
# custom_block, which renders the stanza first and trips Cask/StanzaOrder on
# everything after it. Scoped to that cop so the deprecation cops still fire.
brew style --except-cops=Cask/StanzaOrder "$TAP_CASKS/lstk.rb"

# Passes on a deprecated stanza, so this guards other faults, not #501. Its
# `--online` form checks urls and checksums, but only the tap has a published
# release to check against.
brew audit --cask "$TAP/lstk"

echo "OK: Homebrew linters clean for $CASK"
Loading