Skip to content

feat: forward SIGINT/SIGTERM to wrapped tools for graceful shutdown #1796

feat: forward SIGINT/SIGTERM to wrapped tools for graceful shutdown

feat: forward SIGINT/SIGTERM to wrapped tools for graceful shutdown #1796

Workflow file for this run

name: LSTK CI
on:
push:
branches:
- main
tags:
- "v*.*.*"
pull_request:
workflow_dispatch:
workflow_call:
permissions:
contents: read
pull-requests: write
checks: write
# Cancel superseded runs on the same PR. Non-PR runs (push/tag/release) each
# get a unique group via run_id, so cancel-in-progress can never touch them —
# the reusable workflow_call from the release pipeline is never interrupted.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version-file: .tool-versions
goreleaser-check:
name: GoReleaser Check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Run GoReleaser check
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser
version: "~> v2"
args: check
test-unit:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Run unit tests
run: make test
env:
CREATE_JUNIT_REPORT: "true"
- name: Upload test results
uses: actions/upload-artifact@v7
if: always()
with:
name: unit-test-results
path: test-results.xml
- name: Test report
uses: dorny/test-reporter@v3
if: always()
with:
name: Unit Test Results
path: test-results.xml
reporter: java-junit
test-integration:
name: Integration Tests (${{ matrix.os }}${{ matrix.shard && format(' shard {0}/{1}', matrix.shard, matrix.shard_total) || '' }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
shard: 1
shard_total: 4
- os: ubuntu-latest
shard: 2
shard_total: 4
- os: ubuntu-latest
shard: 3
shard_total: 4
- os: ubuntu-latest
shard: 4
shard_total: 4
- os: macos-latest
- os: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: |
go.sum
test/integration/go.sum
# Install lstk via Homebrew so the Homebrew update integration test
# (TestUpdateHomebrew) can locate a real Caskroom binary path.
- name: Install lstk via Homebrew
if: matrix.os == 'macos-latest'
run: brew install localstack/tap/lstk
# Install Terraform and OpenTofu so the `lstk terraform` end-to-end tests
# (terraform_e2e_test.go) run on the Docker-capable Linux shards. They
# skip automatically wherever the binaries are absent (macOS/Windows).
- name: Install Terraform
if: matrix.os == 'ubuntu-latest'
uses: hashicorp/setup-terraform@v4
with:
terraform_wrapper: false
- name: Install OpenTofu
if: matrix.os == 'ubuntu-latest'
uses: opentofu/setup-opentofu@v2
with:
tofu_wrapper: false
# Install the AWS CDK CLI so the `lstk cdk` end-to-end tests
# (cdk_e2e_test.go) run on the Docker-capable Linux shards. They skip
# automatically wherever cdk is absent (macOS/Windows). lstk requires
# CDK >= 2.177.0; the latest release satisfies that.
- name: Install AWS CDK
if: matrix.os == 'ubuntu-latest'
run: npm install -g aws-cdk
# Install the AWS SAM CLI so the `lstk sam` end-to-end tests
# (sam_e2e_test.go) run on the Docker-capable Linux shards. They skip
# automatically wherever sam is absent (macOS/Windows). The ubuntu-latest
# image currently ships sam, but we install it explicitly so the tests
# don't silently lose coverage if a future image drops it. lstk requires
# SAM >= 1.95.0; the latest release satisfies that.
- name: Install AWS SAM CLI
if: matrix.os == 'ubuntu-latest'
uses: aws-actions/setup-sam@v3
with:
use-installer: true
- name: Run integration tests
run: make test-integration
env:
CREATE_JUNIT_REPORT: "true"
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
LSTK_TEST_HOMEBREW: ${{ matrix.os == 'macos-latest' && '1' || '0' }}
LSTK_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHARD_INDEX: ${{ matrix.shard }}
SHARD_TOTAL: ${{ matrix.shard_total }}
- name: Upload test results
uses: actions/upload-artifact@v7
if: always()
with:
name: integration-test-results-${{ matrix.os }}${{ matrix.shard && format('-{0}', matrix.shard) || '' }}
path: test-integration-results.xml
- name: Test report
uses: dorny/test-reporter@v3
if: always()
with:
name: Integration Test Results (${{ matrix.os }}${{ matrix.shard && format(' shard {0}/{1}', matrix.shard, matrix.shard_total) || '' }})
path: test-integration-results.xml
reporter: java-junit
# Aggregator job under a stable name so branch ruleset can require a single
# status check regardless of how many shards the integration matrix uses.
test-integration-summary:
name: Integration Tests (ubuntu-latest)
runs-on: ubuntu-latest
needs: test-integration
if: always()
timeout-minutes: 5
steps:
- name: Verify integration matrix succeeded
run: |
if [ "${{ needs.test-integration.result }}" != "success" ]; then
echo "test-integration matrix result: ${{ needs.test-integration.result }}"
exit 1
fi
test-launcher:
name: Launcher Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: "20"
- name: Run launcher tests
run: node --test npm/*.test.js
release:
name: Build and Publish Release
if: startsWith(github.ref, 'refs/tags/')
needs:
- lint
- goreleaser-check
- test-unit
- test-integration
- test-launcher
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Validate version tag
run: |
if [[ ! "${GITHUB_REF_NAME}" =~ ^v0\.[0-9]+\.[0-9]+$ ]]; then
echo "Tag '${GITHUB_REF_NAME}' does not match version format v0.minor.patch"
exit 1
fi
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ secrets.PRO_ACCESS_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
# Build the platform packages and main wrapper package from the
# GoReleaser output. This is the same tool the previous
# evg4b/goreleaser-npm-publisher-action wrapped, split into an explicit
# build step so we can swap in our own launcher before publishing.
- name: Build npm packages
run: >
npx --yes goreleaser-npm-publisher@1.5.0 build
--project .
--prefix @localstack
--license Apache-2.0
--description "LocalStack CLI v2 - Start and manage LocalStack emulators"
--files README.md LICENSE
--keywords localstack cli aws emulator docker
# Replace the auto-generated wrapper with our launcher, which forwards
# SIGINT/SIGTERM/SIGHUP to the Go binary (DEVX-942). The generated wrapper
# spawns the binary without signal handlers, so a programmatic kill of the
# Node process would orphan the child, including mid-flight container starts.
- name: Install signal-forwarding launcher
run: cp npm/launcher.js dist/npm/lstk/index.js
- name: Publish to NPM
run: |
for dir in dist/npm/lstk-*/ dist/npm/lstk/; do
npm publish "$dir" --access public
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}