feat: forward SIGINT/SIGTERM to wrapped tools for graceful shutdown #1796
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |