From fbf1f3f38956937194ed79100cc81ef63ecd77cd Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Fri, 23 Oct 2026 16:03:19 +0200 Subject: [PATCH 1/6] Run a few samples on a Windows runner from Git Bash A manual run with the new windows input runs, instead of the Linux matrix, seven samples on windows-latest from Git Bash, with MSYS_NO_PATHCONV=1 set as the README asks Windows users to do: the samples whose scripts pass a Cosmos DB partition key, resource IDs, Front Door paths, or local files read by az, curl, jq, python and zip. The emulator is a Linux image and Docker on the Windows runner runs Windows containers, so the job starts Docker Engine inside an Ubuntu WSL 2 distribution, runs the emulator there, and reaches it from Windows over ports WSL forwards to localhost. lstk finds the running container by its name, so run-samples.sh drives each sample exactly as it does on Linux. Git Bash ships no zip, so a shim hands zip -r to the 7-Zip the runner image ships. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DhZ2ojsEXd2SGMo7s9uiZh --- .github/workflows/run-samples.yml | 193 ++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/.github/workflows/run-samples.yml b/.github/workflows/run-samples.yml index 34711d9..6781a4b 100644 --- a/.github/workflows/run-samples.yml +++ b/.github/workflows/run-samples.yml @@ -29,6 +29,11 @@ on: - amd64 - arm64 default: both + windows: + description: "Run only the Windows job, a few samples from Git Bash with MSYS_NO_PATHCONV=1, instead of the Linux matrix" + required: false + type: boolean + default: false # Defaults for pull_request events (change DEFAULT_RUN_MODE to 'changed' to only run affected tests) env: @@ -42,6 +47,8 @@ jobs: # Lightweight job that builds the dynamic test matrix for the main test jobs setup: name: "Build Test Matrix" + # A manual run with the windows input runs the Windows job alone; skipping this job skips the matrix + if: github.event_name != 'workflow_dispatch' || !inputs.windows runs-on: ubuntu-latest permissions: contents: read @@ -338,3 +345,189 @@ jobs: else make logs fi + + # Runs a few samples on Windows, from Git Bash with MSYS_NO_PATHCONV=1 set as the README asks Windows + # users to do, on manual runs with the windows input only. The emulator is a Linux image and Docker on + # the Windows runner runs Windows containers, so the emulator runs in Docker Engine inside a WSL 2 + # distribution on the same runner; the Windows docker CLI and lstk reach that engine over TCP, which + # WSL forwards to the runner's localhost, and the samples reach the emulator the same way. + windows: + name: "Windows (Git Bash): ${{ matrix.sample }}" + if: github.event_name == 'workflow_dispatch' && inputs.windows + environment: AZURE + runs-on: windows-latest + timeout-minutes: 90 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + # Samples whose scripts hand Git Bash something it would rewrite: a Cosmos DB partition key, + # resource IDs, Front Door paths, or local files read by az, curl, jq, python and zip. + sample: + - web-app-cosmosdb-nosql-api/python/scripts + - web-app-cosmosdb-nosql-api/dotnet/scripts + - web-app-custom-image/python/scripts + - function-app-front-door/python/scripts + - api-management-function-app/python/scripts + - function-app-managed-identity/python/scripts + - eventhubs/python/scripts + defaults: + run: + shell: bash + env: + IMAGE: localstack/localstack-azure:latest + # The Docker Engine in WSL 2, for the Windows docker CLI and lstk + DOCKER_HOST: tcp://127.0.0.1:2375 + # What the README asks Windows users of Git Bash and MSYS2 to export + MSYS_NO_PATHCONV: "1" + # A virtual environment on Windows has python.exe but no python3 + PYTHON_BIN: python + + steps: + - name: Checkout repo + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Configure WSL + # Once a step's wsl.exe session ends, WSL stops the idle distribution after 15 seconds, and + # dockerd with it; -1 keeps it running for the whole job. The memory leaves room for the backing + # containers the samples start. + shell: pwsh + run: | + Set-Content -Path "$env:USERPROFILE\.wslconfig" -Value "[wsl2]`nmemory=12GB`n`n[general]`ninstanceIdleTimeout=-1" + wsl --shutdown + + - name: Set up Ubuntu in WSL 2 + uses: Vampire/setup-wsl@d1da7f2c0322a5ee4f24975344f67fc0f5baf364 # v7.0.0 + with: + distribution: Ubuntu-24.04 + wsl-version: 2 + wsl-shell-user: root + additional-packages: docker.io + + - name: Start Docker Engine in WSL 2 + # The Unix socket is what the emulator container mounts to start its backing containers; the TCP + # endpoint, bound to localhost, is what WSL forwards to Windows for the docker CLI and lstk. + shell: wsl-bash {0} + run: | + setsid nohup dockerd --host unix:///var/run/docker.sock --host tcp://127.0.0.1:2375 --tls=false \ + > /var/log/dockerd.log 2>&1 < /dev/null & + for _ in $(seq 1 60); do + docker info > /dev/null 2>&1 && break + sleep 2 + done + docker info --format 'Docker {{.ServerVersion}} on {{.OperatingSystem}}, kernel {{.KernelVersion}}' + + - name: Check that the Windows docker CLI reaches it + run: docker version + + - name: Login to Docker Hub + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + username: ${{ secrets.DOCKERHUB_PULL_USERNAME }} + password: ${{ secrets.DOCKERHUB_PULL_TOKEN }} + + - name: Start LocalStack in WSL 2 + # Started with the docker CLI rather than lstk: lstk on Windows would mount a Windows volume path, + # and no Docker socket, into the Linux engine. The two mounts are paths inside WSL, which + # MSYS_NO_PATHCONV keeps Git Bash from rewriting. run-samples.sh's own `lstk start` then finds the + # running container by its name, localstack-azure, and leaves it alone. + env: + LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} + GITHUB_API_TOKEN: ${{ github.token }} + run: | + docker pull "$IMAGE" + docker run --detach --name localstack-azure \ + --publish 127.0.0.1:4566:4566 --publish 127.0.0.1:443:443 \ + --publish 127.0.0.1:4510-4559:4510-4559 \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + --volume /var/lib/localstack-azure:/var/lib/localstack \ + --env LOCALSTACK_AUTH_TOKEN --env GITHUB_API_TOKEN \ + --env MSSQL_ACCEPT_EULA=Y --env LS_LOG=debug --env DISABLE_EVENTS=1 --env DNS_ADDRESS=0 \ + "$IMAGE" + for _ in $(seq 1 200); do + if curl --silent --fail --max-time 5 http://127.0.0.1:4566/_localstack/health > /dev/null; then + echo "Emulator ready" + exit 0 + fi + sleep 2 + done + echo "::error::The emulator did not answer on 127.0.0.1:4566 within 400s" + docker logs --tail 100 localstack-azure + exit 1 + + - name: Install lstk CLI + # The release the Linux job pins + run: npm install -g @localstack/lstk@1.4.0 + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3.12' + + - name: Set up .NET + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 + with: + dotnet-version: '10.0' + + - name: Install Terraform + # run-samples.sh refuses to start without it, whichever sample it runs + uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: "1.5.0" + terraform_wrapper: false + + - name: Provide zip + # The scripts package apps with `zip -r ARCHIVE FILES...`, and Git Bash ships no zip. 7-Zip, which + # the runner image ships, writes the same archive; being a Windows program, it gets the Windows + # form of any absolute path. + run: | + mkdir -p "$RUNNER_TEMP/bin" + cat > "$RUNNER_TEMP/bin/zip" <<'EOF' + #!/bin/bash + seven_zip=$(command -v 7z || echo "/c/Program Files/7-Zip/7z.exe") + args=() + for arg in "$@"; do + case "$arg" in + -*) ;; + /*) args+=("$(cygpath -m "$arg")") ;; + *) args+=("$arg") ;; + esac + done + exec "$seven_zip" a -tzip -bso0 -bsp0 "${args[@]}" + EOF + echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" + + - name: Install the Python dependencies + run: | + python -m venv .venv + .venv/Scripts/python -m pip install --upgrade pip setuptools wheel + .venv/Scripts/python -m pip install -r requirements-dev.txt + + - name: "Run: ${{ matrix.sample }}" + # run-samples.sh routes az to the emulator with `lstk az start-interception`, as on Linux, and runs + # exactly this sample when given its shard: its 1-based position in the full list. + env: + SAMPLE: ${{ matrix.sample }} + LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} + run: | + # The runner's jq.exe is a Windows program, which ends its lines with CRLF; the CR would stick to SPLITS + read -r SHARD SPLITS < <(bash ./run-samples.sh --list | + jq -r --arg name "$SAMPLE" '.[] | select(.name == $name) | "\(.shard) \(.splits)"' | tr -d '\r') + if [[ -z "$SHARD" ]]; then + echo "::error::[$SAMPLE] is not registered in run-samples.sh" + exit 1 + fi + source .venv/Scripts/activate + bash ./run-samples.sh "$SHARD" "$SPLITS" + + - name: Get LocalStack Logs + if: always() + run: docker logs localstack-azure 2>&1 | tail -n 500 || true + + - name: Get the Docker Engine log + if: failure() + shell: wsl-bash {0} + run: tail -n 100 /var/log/dockerd.log || true From 6cf3220bf3a848f37762d90a85a5976b4d9813ad Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Fri, 23 Oct 2026 16:32:24 +0200 Subject: [PATCH 2/6] Start the Windows job's Docker Engine on TCP after stopping the packaged one Installing docker.io in the WSL distribution already starts a Docker daemon on the Unix socket alone, so the daemon the job started with TCP port 2375 exited at once ("process with PID 1063 is still running"), the readiness check passed against the packaged daemon, and the Windows docker CLI found nothing on the port. The step now stops the packaged daemon (its systemd units, or its PID file without systemd), starts its own, and waits on the TCP endpoint. The Docker Engine log step runs only once WSL is set up, since its wsl-bash shell does not exist before that. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DhZ2ojsEXd2SGMo7s9uiZh --- .github/workflows/run-samples.yml | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-samples.yml b/.github/workflows/run-samples.yml index 6781a4b..22d5448 100644 --- a/.github/workflows/run-samples.yml +++ b/.github/workflows/run-samples.yml @@ -400,6 +400,7 @@ jobs: wsl --shutdown - name: Set up Ubuntu in WSL 2 + id: wsl uses: Vampire/setup-wsl@d1da7f2c0322a5ee4f24975344f67fc0f5baf364 # v7.0.0 with: distribution: Ubuntu-24.04 @@ -408,17 +409,34 @@ jobs: additional-packages: docker.io - name: Start Docker Engine in WSL 2 - # The Unix socket is what the emulator container mounts to start its backing containers; the TCP - # endpoint, bound to localhost, is what WSL forwards to Windows for the docker CLI and lstk. + # Installing docker.io already started a daemon on the Unix socket alone, and a second one cannot + # start beside it, so it is stopped and replaced by one that also listens on TCP. The Unix socket + # is what the emulator container mounts to start its backing containers; the TCP endpoint, bound + # to localhost, is what WSL forwards to Windows for the docker CLI and lstk. The wait asks the TCP + # endpoint, so the step fails when that daemon did not start. shell: wsl-bash {0} run: | + if [ -d /run/systemd/system ]; then + systemctl disable --now docker.socket docker.service + elif [ -e /var/run/docker.pid ]; then + kill "$(cat /var/run/docker.pid)" + fi + for _ in $(seq 1 30); do + [ -e /var/run/docker.pid ] || break + sleep 1 + done + if [ -e /var/run/docker.pid ]; then + echo "::error::The Docker daemon that installing docker.io started is still running" + exit 1 + fi setsid nohup dockerd --host unix:///var/run/docker.sock --host tcp://127.0.0.1:2375 --tls=false \ > /var/log/dockerd.log 2>&1 < /dev/null & for _ in $(seq 1 60); do - docker info > /dev/null 2>&1 && break + docker --host tcp://127.0.0.1:2375 info > /dev/null 2>&1 && break sleep 2 done - docker info --format 'Docker {{.ServerVersion}} on {{.OperatingSystem}}, kernel {{.KernelVersion}}' + docker --host tcp://127.0.0.1:2375 info \ + --format 'Docker {{.ServerVersion}} on {{.OperatingSystem}}, kernel {{.KernelVersion}}' - name: Check that the Windows docker CLI reaches it run: docker version @@ -528,6 +546,7 @@ jobs: run: docker logs localstack-azure 2>&1 | tail -n 500 || true - name: Get the Docker Engine log - if: failure() + # The wsl-bash shell exists only once WSL is set up + if: failure() && steps.wsl.outcome == 'success' shell: wsl-bash {0} run: tail -n 100 /var/log/dockerd.log || true From 7d43db91cae70c6a96ab9b8ae0b14592a7e26d14 Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Fri, 23 Oct 2026 17:00:10 +0200 Subject: [PATCH 3/6] Run every sample in the Windows job The Windows job takes its matrix from the setup job, which on a manual run with the windows input selects every sample on amd64, the architecture of the Windows runner, and the Linux matrix stays out of that run. It now provides what the remaining samples need: Java for the Service Bus sample, the zip MSYS2 ships (the 7-Zip shim dropped zip's -x exclusions), psql from the PostgreSQL the image installs, and the Go sqlcmd release, pinned with its SHA-256. As in the Linux job, a failed sample is retried once on a fresh emulator. lstk 1.4.0 cannot register its Azure CLI cloud on Windows when the CLI sits under C:\\Program Files: cmd.exe cuts the command line that carries the cloud configuration's JSON at that space. A junction without spaces, first on the PATH, works around it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DhZ2ojsEXd2SGMo7s9uiZh --- .github/workflows/run-samples.yml | 175 ++++++++++++++++++++---------- 1 file changed, 117 insertions(+), 58 deletions(-) diff --git a/.github/workflows/run-samples.yml b/.github/workflows/run-samples.yml index 22d5448..23a4a86 100644 --- a/.github/workflows/run-samples.yml +++ b/.github/workflows/run-samples.yml @@ -30,7 +30,7 @@ on: - arm64 default: both windows: - description: "Run only the Windows job, a few samples from Git Bash with MSYS_NO_PATHCONV=1, instead of the Linux matrix" + description: "Run only the Windows job, every sample from Git Bash with MSYS_NO_PATHCONV=1, instead of the Linux matrix (run_mode and arch do not apply)" required: false type: boolean default: false @@ -47,8 +47,6 @@ jobs: # Lightweight job that builds the dynamic test matrix for the main test jobs setup: name: "Build Test Matrix" - # A manual run with the windows input runs the Windows job alone; skipping this job skips the matrix - if: github.event_name != 'workflow_dispatch' || !inputs.windows runs-on: ubuntu-latest permissions: contents: read @@ -68,9 +66,14 @@ jobs: # 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 }} + WINDOWS_INPUT: ${{ github.event.inputs.windows }} run: | - # Pick run mode and architecture: manual dispatch uses the dropdowns, PRs use the env defaults - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + # Pick run mode and architecture: manual dispatch uses the dropdowns, PRs use the env defaults. + # A manual run with the windows input runs every sample on amd64, the architecture of the Windows runner. + if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${WINDOWS_INPUT}" == "true" ]]; then + RUN_MODE=all + ARCH=amd64 + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then RUN_MODE="${RUN_MODE_INPUT}" ARCH="${ARCH_INPUT}" else @@ -94,7 +97,8 @@ jobs: scripts: name: "Test: ${{ matrix.name }}" needs: setup - if: needs.setup.outputs.has_tests == 'true' # Skip entirely when no tests match + # Skip entirely when no tests match, and on a manual run with the windows input, which runs the Windows job instead + if: needs.setup.outputs.has_tests == 'true' && !(github.event_name == 'workflow_dispatch' && inputs.windows) environment: AZURE strategy: fail-fast: false @@ -346,14 +350,15 @@ jobs: make logs fi - # Runs a few samples on Windows, from Git Bash with MSYS_NO_PATHCONV=1 set as the README asks Windows - # users to do, on manual runs with the windows input only. The emulator is a Linux image and Docker on - # the Windows runner runs Windows containers, so the emulator runs in Docker Engine inside a WSL 2 - # distribution on the same runner; the Windows docker CLI and lstk reach that engine over TCP, which - # WSL forwards to the runner's localhost, and the samples reach the emulator the same way. + # Runs every sample on Windows, from Git Bash with MSYS_NO_PATHCONV=1 set as the README asks Windows users to do, + # on manual runs with the windows input only. The emulator is a Linux image and Docker on the Windows runner runs + # Windows containers, so the emulator runs in Docker Engine inside a WSL 2 distribution on the same runner; the + # Windows docker CLI and lstk reach that engine over TCP, which WSL forwards to the runner's localhost, and the + # samples reach the emulator the same way. windows: - name: "Windows (Git Bash): ${{ matrix.sample }}" - if: github.event_name == 'workflow_dispatch' && inputs.windows + name: "Windows (Git Bash): ${{ matrix.name }}" + needs: setup + if: github.event_name == 'workflow_dispatch' && inputs.windows && needs.setup.outputs.has_tests == 'true' environment: AZURE runs-on: windows-latest timeout-minutes: 90 @@ -361,17 +366,8 @@ jobs: contents: read strategy: fail-fast: false - matrix: - # Samples whose scripts hand Git Bash something it would rewrite: a Cosmos DB partition key, - # resource IDs, Front Door paths, or local files read by az, curl, jq, python and zip. - sample: - - web-app-cosmosdb-nosql-api/python/scripts - - web-app-cosmosdb-nosql-api/dotnet/scripts - - web-app-custom-image/python/scripts - - function-app-front-door/python/scripts - - api-management-function-app/python/scripts - - function-app-managed-identity/python/scripts - - eventhubs/python/scripts + # Every sample: on a manual run with the windows input, the setup job selects all of them, on amd64 + matrix: ${{ fromJSON(needs.setup.outputs.matrix) }} defaults: run: shell: bash @@ -451,12 +447,14 @@ jobs: # Started with the docker CLI rather than lstk: lstk on Windows would mount a Windows volume path, # and no Docker socket, into the Linux engine. The two mounts are paths inside WSL, which # MSYS_NO_PATHCONV keeps Git Bash from rewriting. run-samples.sh's own `lstk start` then finds the - # running container by its name, localstack-azure, and leaves it alone. + # running container by its name, localstack-azure, and leaves it alone. The script stays in + # RUNNER_TEMP for the retry, which starts a fresh emulator the same way. env: LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} GITHUB_API_TOKEN: ${{ github.token }} run: | - docker pull "$IMAGE" + cat > "$RUNNER_TEMP/start-emulator.sh" <<'EOF' + #!/bin/bash docker run --detach --name localstack-azure \ --publish 127.0.0.1:4566:4566 --publish 127.0.0.1:443:443 \ --publish 127.0.0.1:4510-4559:4510-4559 \ @@ -464,7 +462,7 @@ jobs: --volume /var/lib/localstack-azure:/var/lib/localstack \ --env LOCALSTACK_AUTH_TOKEN --env GITHUB_API_TOKEN \ --env MSSQL_ACCEPT_EULA=Y --env LS_LOG=debug --env DISABLE_EVENTS=1 --env DNS_ADDRESS=0 \ - "$IMAGE" + "$IMAGE" || exit 1 for _ in $(seq 1 200); do if curl --silent --fail --max-time 5 http://127.0.0.1:4566/_localstack/health > /dev/null; then echo "Emulator ready" @@ -475,6 +473,21 @@ jobs: echo "::error::The emulator did not answer on 127.0.0.1:4566 within 400s" docker logs --tail 100 localstack-azure exit 1 + EOF + docker pull "$IMAGE" + bash "$RUNNER_TEMP/start-emulator.sh" + + - name: Give lstk an Azure CLI path without spaces + # lstk sets up the Azure CLI with `az cloud register --cloud-config `, run through az.cmd, and + # cmd.exe cuts that command line at the space of "C:\Program Files" once the JSON's quotes are in it + # ("'C:\Program' is not recognized"), so `lstk az start-interception` (lstk 1.4.0) fails wherever the + # Azure CLI sits in its default folder. A junction without spaces, first on the PATH, works around it + # until lstk passes the JSON another way; Git Bash runs the same Azure CLI through it. + shell: pwsh + run: | + $cliDir = Split-Path -Parent (Split-Path -Parent (Get-Command az.cmd).Source) + New-Item -ItemType Junction -Path C:\azure-cli -Target $cliDir | Out-Null + Add-Content -Path $env:GITHUB_PATH -Value 'C:\azure-cli\wbin' - name: Install lstk CLI # The release the Linux job pins @@ -490,6 +503,13 @@ jobs: with: dotnet-version: '10.0' + - name: Set up Java + # The Service Bus sample builds its Java app with Maven, which the runner image ships + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '25' + - name: Install Terraform # run-samples.sh refuses to start without it, whichever sample it runs uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 @@ -497,26 +517,43 @@ jobs: terraform_version: "1.5.0" terraform_wrapper: false - - name: Provide zip - # The scripts package apps with `zip -r ARCHIVE FILES...`, and Git Bash ships no zip. 7-Zip, which - # the runner image ships, writes the same archive; being a Windows program, it gets the Windows - # form of any absolute path. + - name: Install zip from MSYS2 + # The samples package apps with zip, which Git Bash does not ship. The runner image has MSYS2, whose + # zip, like a zip.exe built for Windows, opens the C:/... paths the scripts hand it. + shell: pwsh + run: C:\msys64\usr\bin\bash.exe -lc 'pacman -Sy --noconfirm --needed zip' + + - name: Put zip, psql and sqlcmd on the PATH + # zip runs from MSYS2's own folder, next to the libraries it was built with. psql comes with the + # PostgreSQL the runner image installs off the PATH; mysql is on it already. sqlcmd, which the Azure + # SQL Database sample seeds its database with, is the Go sqlcmd release, pinned with its SHA-256. + env: + SQLCMD_VERSION: v1.10.0 + SQLCMD_SHA256: a4c28332fcc6e497d655e53ac8f4939f4ab170f9cfd32d0fa5081b60f4d9d691 run: | - mkdir -p "$RUNNER_TEMP/bin" - cat > "$RUNNER_TEMP/bin/zip" <<'EOF' - #!/bin/bash - seven_zip=$(command -v 7z || echo "/c/Program Files/7-Zip/7z.exe") - args=() - for arg in "$@"; do - case "$arg" in - -*) ;; - /*) args+=("$(cygpath -m "$arg")") ;; - *) args+=("$arg") ;; - esac - done - exec "$seven_zip" a -tzip -bso0 -bsp0 "${args[@]}" - EOF - echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" + bin_dir="$RUNNER_TEMP/bin" + mkdir -p "$bin_dir" + printf '#!/bin/bash\nexec /c/msys64/usr/bin/zip.exe "$@"\n' > "$bin_dir/zip" + curl --silent --show-error --fail --location --retry 3 --output "$RUNNER_TEMP/sqlcmd.zip" \ + "https://github.com/microsoft/go-sqlcmd/releases/download/${SQLCMD_VERSION}/sqlcmd-windows-amd64.zip" + echo "${SQLCMD_SHA256} $RUNNER_TEMP/sqlcmd.zip" | sha256sum --check + unzip -o -q -j "$RUNNER_TEMP/sqlcmd.zip" sqlcmd.exe -d "$bin_dir" + cygpath -w "$bin_dir" >> "$GITHUB_PATH" + echo "$PGBIN" >> "$GITHUB_PATH" + + - name: Show the tool versions + run: | + zip -v | sed -n 2p + psql --version + mysql --version + sqlcmd --version + java -version 2>&1 | sed -n 1p + mvn -v | sed -n 1p + dotnet --version + terraform version | sed -n 1p + az version --output tsv --query '"azure-cli"' + jq --version + unzip -v | sed -n 1p - name: Install the Python dependencies run: | @@ -524,26 +561,48 @@ jobs: .venv/Scripts/python -m pip install --upgrade pip setuptools wheel .venv/Scripts/python -m pip install -r requirements-dev.txt - - name: "Run: ${{ matrix.sample }}" - # run-samples.sh routes az to the emulator with `lstk az start-interception`, as on Linux, and runs - # exactly this sample when given its shard: its 1-based position in the full list. + - name: "Run: ${{ matrix.name }}" + # run-samples.sh routes az to the emulator with `lstk az start-interception`, as on Linux, and runs exactly + # this sample: SPLITS is the number of samples and SHARD its 1-based position among them. + id: initial_run + # As in the Linux job, a transient error fails a sample now and then, so a failure is retried once on a + # fresh emulator, and the retry decides the job's outcome: a sample that is really broken fails both. + continue-on-error: true env: - SAMPLE: ${{ matrix.sample }} + SHARD: ${{ matrix.shard }} + SPLITS: ${{ matrix.splits }} LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} run: | - # The runner's jq.exe is a Windows program, which ends its lines with CRLF; the CR would stick to SPLITS - read -r SHARD SPLITS < <(bash ./run-samples.sh --list | - jq -r --arg name "$SAMPLE" '.[] | select(.name == $name) | "\(.shard) \(.splits)"' | tr -d '\r') - if [[ -z "$SHARD" ]]; then - echo "::error::[$SAMPLE] is not registered in run-samples.sh" - exit 1 - fi + source .venv/Scripts/activate + bash ./run-samples.sh "$SHARD" "$SPLITS" + + - name: "Retry: ${{ matrix.name }}" + # The first emulator's logs are kept for the Get LocalStack Logs step. Removing every container also + # removes the backing containers the first emulator started, which the fresh one would collide with. + if: steps.initial_run.outcome == 'failure' + env: + MATRIX_NAME: ${{ matrix.name }} + SHARD: ${{ matrix.shard }} + SPLITS: ${{ matrix.splits }} + LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} + GITHUB_API_TOKEN: ${{ github.token }} + run: | + echo "::warning::${MATRIX_NAME} failed once; retrying on a fresh emulator." + docker logs localstack-azure > "$RUNNER_TEMP/localstack-azure-first-attempt.log" 2>&1 || true + docker ps --all --quiet | xargs --no-run-if-empty docker rm --force > /dev/null + bash "$RUNNER_TEMP/start-emulator.sh" source .venv/Scripts/activate bash ./run-samples.sh "$SHARD" "$SPLITS" - name: Get LocalStack Logs if: always() - run: docker logs localstack-azure 2>&1 | tail -n 500 || true + run: | + if [ -s "$RUNNER_TEMP/localstack-azure-first-attempt.log" ]; then + echo "===== First attempt =====" + tail -n 500 "$RUNNER_TEMP/localstack-azure-first-attempt.log" + echo "===== Retry =====" + fi + docker logs localstack-azure 2>&1 | tail -n 500 || true - name: Get the Docker Engine log # The wsl-bash shell exists only once WSL is set up From 113c9828765934d966c1092522722a111a8368c4 Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Fri, 23 Oct 2026 17:09:18 +0200 Subject: [PATCH 4/6] Print Maven's version with the path conversion on, as the Service Bus sample runs it Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DhZ2ojsEXd2SGMo7s9uiZh --- .github/workflows/run-samples.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-samples.yml b/.github/workflows/run-samples.yml index 23a4a86..6966dc9 100644 --- a/.github/workflows/run-samples.yml +++ b/.github/workflows/run-samples.yml @@ -548,7 +548,7 @@ jobs: mysql --version sqlcmd --version java -version 2>&1 | sed -n 1p - mvn -v | sed -n 1p + (unset MSYS_NO_PATHCONV; mvn -v) | sed -n 1p dotnet --version terraform version | sed -n 1p az version --output tsv --query '"azure-cli"' From bf49df624b97c62cdf813c4f6ddbeab4b12bd3e7 Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Fri, 23 Oct 2026 17:28:21 +0200 Subject: [PATCH 5/6] Drop the carriage returns of Windows programs in the Windows job, as the README asks The README asks Windows users for set -o igncr and export SHELLOPTS. Exporting SHELLOPTS in a step would also hand the step's own -e and pipefail to the sample scripts, so the job turns the option on for every bash through BASH_ENV instead. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DhZ2ojsEXd2SGMo7s9uiZh --- .github/workflows/run-samples.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/run-samples.yml b/.github/workflows/run-samples.yml index 6966dc9..d1bad9d 100644 --- a/.github/workflows/run-samples.yml +++ b/.github/workflows/run-samples.yml @@ -386,6 +386,15 @@ jobs: with: persist-credentials: false + - name: Drop the carriage returns of Windows programs + # The README asks Windows users for `set -o igncr` and `export SHELLOPTS`, so that the bash of Git Bash + # and MSYS2 drops the carriage returns the Azure CLI and jq print from $(...) and read. Exporting + # SHELLOPTS here would also hand each step's own -e and pipefail to the sample scripts, so every bash + # turns the option on through BASH_ENV instead; a bash without the option skips the line. + run: | + printf '(set -o igncr) 2>/dev/null && set -o igncr\n' > "$RUNNER_TEMP/igncr.sh" + echo "BASH_ENV=$(cygpath -m "$RUNNER_TEMP/igncr.sh")" >> "$GITHUB_ENV" + - name: Configure WSL # Once a step's wsl.exe session ends, WSL stops the idle distribution after 15 seconds, and # dockerd with it; -1 keeps it running for the whole job. The memory leaves room for the backing From 3ed7db1e2f478f46551dc4ee767275b1ad4d5b20 Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Fri, 23 Oct 2026 18:45:13 +0200 Subject: [PATCH 6/6] Turn off WSL's DNS tunneling in the Windows job With DNS tunneling, on by default, the containers of the Docker Engine in WSL could not reach the 10.255.255.254 resolver WSL configures, so their lookups timed out: the Docker Engine log held dozens of "failed to query external DNS server" errors, and the Container Apps runtime could not pull the sample's image (ImagePullBackOff). Without tunneling, WSL hands out the NAT device on the host as the DNS server, which containers reach too (https://learn.microsoft.com/windows/wsl/troubleshooting#common-issues). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DhZ2ojsEXd2SGMo7s9uiZh --- .github/workflows/run-samples.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-samples.yml b/.github/workflows/run-samples.yml index d1bad9d..246f1df 100644 --- a/.github/workflows/run-samples.yml +++ b/.github/workflows/run-samples.yml @@ -399,9 +399,12 @@ jobs: # Once a step's wsl.exe session ends, WSL stops the idle distribution after 15 seconds, and # dockerd with it; -1 keeps it running for the whole job. The memory leaves room for the backing # containers the samples start. + # DNS tunneling, on by default, answers DNS for WSL itself, but the containers of the Docker Engine + # in WSL cannot reach its 10.255.255.254 resolver, so their lookups time out; without it, WSL + # hands out the NAT device on the host, which containers reach too. shell: pwsh run: | - Set-Content -Path "$env:USERPROFILE\.wslconfig" -Value "[wsl2]`nmemory=12GB`n`n[general]`ninstanceIdleTimeout=-1" + Set-Content -Path "$env:USERPROFILE\.wslconfig" -Value "[wsl2]`nmemory=12GB`ndnsTunneling=false`n`n[general]`ninstanceIdleTimeout=-1" wsl --shutdown - name: Set up Ubuntu in WSL 2