Add an Azure Container Apps sample (guestbook on ACR + Blob Storage) #321
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: Samples CI | |
| # Tests Azure samples against the LocalStack emulator. | |
| # Each test runs in its own job. Set DEFAULT_RUN_MODE to 'changed' to only run affected tests. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | |
| cancel-in-progress: true | |
| on: | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| inputs: | |
| run_mode: | |
| description: "Test mode: 'all' runs every test, 'changed' runs only tests with modified files" | |
| required: false | |
| type: choice | |
| options: | |
| - all | |
| - changed | |
| default: changed | |
| arch: | |
| description: "Architecture: 'both' runs every test on each architecture it supports" | |
| required: false | |
| type: choice | |
| options: | |
| - both | |
| - amd64 | |
| - arm64 | |
| default: both | |
| # Defaults for pull_request events (change DEFAULT_RUN_MODE to 'changed' to only run affected tests) | |
| env: | |
| DEFAULT_RUN_MODE: changed | |
| DEFAULT_ARCH: both | |
| # Least-privilege default: no permissions unless a job explicitly opts in. | |
| permissions: {} | |
| jobs: | |
| # Lightweight job that builds the dynamic test matrix for the main test jobs | |
| setup: | |
| name: "Build Test Matrix" | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| matrix: ${{ steps.build-matrix.outputs.matrix }} | |
| has_tests: ${{ steps.build-matrix.outputs.has_tests }} | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 # Full history needed for git diff in "changed" mode | |
| persist-credentials: false | |
| - name: Build dynamic matrix | |
| id: build-matrix | |
| env: | |
| # Untrusted dispatch inputs routed via env, not interpolated into the shell (template-injection) | |
| RUN_MODE_INPUT: ${{ github.event.inputs.run_mode }} | |
| ARCH_INPUT: ${{ github.event.inputs.arch }} | |
| run: | | |
| # Pick run mode and architecture: manual dispatch uses the dropdowns, PRs use the env defaults | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| RUN_MODE="${RUN_MODE_INPUT}" | |
| ARCH="${ARCH_INPUT}" | |
| else | |
| RUN_MODE="${{ env.DEFAULT_RUN_MODE }}" | |
| ARCH="${{ env.DEFAULT_ARCH }}" | |
| fi | |
| # In "changed" mode, determine the base commit to diff against | |
| BASE_SHA="" | |
| if [[ "$RUN_MODE" == "changed" ]]; then | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | |
| else | |
| BASE_SHA=$(git rev-parse origin/main 2>/dev/null || git rev-parse HEAD~1) | |
| fi | |
| fi | |
| .github/scripts/build-matrix.sh "$RUN_MODE" "$BASE_SHA" "$ARCH" | |
| # Each test runs in its own job — one matrix entry per test/architecture from the setup job | |
| scripts: | |
| name: "Test: ${{ matrix.name }}" | |
| needs: setup | |
| if: needs.setup.outputs.has_tests == 'true' # Skip entirely when no tests match | |
| environment: AZURE | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.setup.outputs.matrix) }} | |
| # ubuntu-22.04 for amd64, ubuntu-22.04-arm for arm64 — the matrix carries the label. | |
| # GitHub-hosted arm64 runners are free for public repositories. | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| env: | |
| IMAGE_NAME: localstack/localstack-azure | |
| DEFAULT_TAG: latest | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Set up environment | |
| env: | |
| RUNNER_TEMP_DIR: ${{ runner.temp }} | |
| run: echo "AZURE_CONFIG_DIR=${RUNNER_TEMP_DIR}/azure-cli" >> "$GITHUB_ENV" | |
| - name: Set up Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: '3.12' | |
| cache: 'pip' | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 | |
| with: | |
| dotnet-version: '10.0' | |
| - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '25' | |
| - name: Install System Dependencies | |
| # Essential tools for script execution, app packaging, and database connectivity. | |
| # jq: for parsing JSON responses from Azure CLI. | |
| # zip: for packaging function/web apps. | |
| # unixodbc-dev & libsnappy-dev: required for Python database drivers (pyodbc, pymongo). | |
| # default-mysql-client: provides the `mysql` CLI used by the MySQL flexible-server sample | |
| # to create the application user and seed the database schema in the local emulator. | |
| # postgresql-client: provides the `psql` CLI used by the PostgreSQL flexible-server sample | |
| # to create the application role and seed the database schema in the local emulator. | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq zip unixodbc-dev libsnappy-dev default-mysql-client postgresql-client | |
| find . -name "*.sh" -exec chmod +x {} + | |
| - name: Pin Azure CLI to 2.88.0 | |
| # SHORTCUT: runner image ubuntu22/20260810.260 bumped the preinstalled az to | |
| # 2.89.0, whose `az mysql flexible-server firewall-rule create` payload the | |
| # emulator answers with a 500 (AttributeError on a None `properties` in | |
| # mysql_flexible/apis/firewall_rules.py). Remove this pin once the released | |
| # emulator image parses the az 2.89 request shape. Both runner arches are | |
| # jammy, so one version string covers amd64 and arm64. | |
| run: sudo apt-get install -y --allow-downgrades azure-cli=2.88.0-1~jammy | |
| - name: Install Terraform | |
| uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 | |
| with: | |
| terraform_version: "1.5.0" | |
| terraform_wrapper: false | |
| - name: Install test dependencies | |
| # Mirroring the localstack-pro approach: install all Python dependencies | |
| # (including the localstack CLI) into a virtual environment to avoid system-level conflicts. | |
| run: make install | |
| - name: Login to Docker Hub | |
| # Mandatory login to Docker Hub to benefit from higher rate limits for authenticated pulls. | |
| # This prevents '429 Too Many Requests' errors during the pull of large emulator images. | |
| uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_PULL_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_PULL_TOKEN }} | |
| - name: Free up disk space | |
| # Azure emulator images are large. Pruning unused Docker objects ensures enough | |
| # disk space is available on the GitHub runner for image pulls and sidecar containers. | |
| run: | | |
| docker system prune -af --volumes | |
| docker builder prune -af | |
| - name: Pull LocalStack Azure Image | |
| # Explicitly pull the image before starting. This mirrors the "Build Docker Image" | |
| # step in localstack-pro and ensures the pull logic is separated from the start logic. | |
| run: docker pull ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} | |
| - name: Start LocalStack | |
| # Run the emulator in detached mode using the virtual environment. | |
| # The readiness wait is generous because the architecture matrix roughly doubles | |
| # the number of concurrent jobs, and the contention for Docker pulls and runner | |
| # I/O pushed some starts past a 120s budget - and, on the slowest runners, | |
| # occasionally past 300s as well. A healthy emulator still returns as soon as | |
| # it is ready, so a larger timeout costs nothing on the happy path. | |
| run: | | |
| source .venv/bin/activate | |
| python -m localstack_cli.cli.main start -d | |
| python -m localstack_cli.cli.main wait -t 600 | |
| env: | |
| IMAGE_NAME: ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} | |
| LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} | |
| DOCKER_FLAGS: "-e MSSQL_ACCEPT_EULA=Y" | |
| LS_LOG: "DEBUG" | |
| DISABLE_EVENTS: "1" | |
| ACTIVATE_PRO: "1" | |
| DNS_ADDRESS: "0" | |
| - name: Install lstk CLI | |
| # Required by run-samples.sh to proxy Azure CLI calls to the emulator | |
| # via `lstk az` (replaces the deprecated azlocal wrapper). | |
| # https://github.com/localstack/lstk | |
| # | |
| # Pinned, like every action in this workflow: an unpinned install means an upstream | |
| # release lands in every run at once, with no commit to point at when things break. | |
| # Bump deliberately. | |
| env: | |
| # 0.19.0, not the current 0.20.0. Since 0.20.0 was published (2026-07-30 11:10) every | |
| # sample run has failed at `lstk az start-interception` with "LocalStack Azure Emulator | |
| # is not running" - 6 runs across 4 unrelated branches, while the emulator was up and | |
| # answering its health endpoint. Detection fails on roughly 80% of fresh runners, is | |
| # decided before the first call (retries never recover it), and reproduces on neither | |
| # arch locally. Every run on 0.19.0 was green. | |
| # Revert to the latest release once the detection regression is fixed upstream. | |
| LSTK_VERSION: "0.19.0" | |
| run: | | |
| MAX_RETRIES=3 | |
| DELAY=15 | |
| for i in $(seq 1 $MAX_RETRIES); do | |
| echo "Attempt $i/$MAX_RETRIES..." | |
| npm install -g "@localstack/lstk@${LSTK_VERSION}" && break | |
| if [ "$i" -eq "$MAX_RETRIES" ]; then | |
| echo "All $MAX_RETRIES attempts failed" | |
| exit 1 | |
| fi | |
| echo "Retrying in ${DELAY}s..." | |
| sleep $DELAY | |
| DELAY=$((DELAY * 2)) | |
| done | |
| command -v lstk | |
| - name: Install MSSQL ODBC and Tools | |
| # Required for the 'web-app-sql-database' sample which uses 'sqlcmd' to | |
| # initialize and verify the database schema in the local emulator. | |
| # amd64 only: the sample is backed by mcr.microsoft.com/mssql/server, which has | |
| # no arm64 image, so it never runs on the arm64 runners. | |
| if: matrix.arch == 'amd64' | |
| run: | | |
| sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list | |
| curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc | |
| curl "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list" | sudo tee /etc/apt/sources.list.d/mssql-release.list | |
| sudo apt-get update | |
| sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 mssql-tools18 | |
| echo "/opt/mssql-tools18/bin" >> "$GITHUB_PATH" | |
| - name: "Run: ${{ matrix.name }}" | |
| # Each job runs exactly one test. SPLITS equals the total test count, and SHARD | |
| # is the 1-based index of this specific test, so run-samples.sh executes only it. | |
| run: make test SHARD="${SHARD}" SPLITS="${SPLITS}" | |
| env: | |
| # Dynamic matrix values routed via env, not interpolated into the shell (template-injection) | |
| SHARD: ${{ matrix.shard }} | |
| SPLITS: ${{ matrix.splits }} | |
| LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} | |
| PURGE_DOCKER: "1" | |
| - name: Get LocalStack Logs | |
| # Captured on failure or success to provide a detailed audit trail of the emulator's activity. | |
| if: always() | |
| run: make logs |