diff --git a/.github/scripts/build-matrix.sh b/.github/scripts/build-matrix.sh index 092a94d..3e6ab30 100755 --- a/.github/scripts/build-matrix.sh +++ b/.github/scripts/build-matrix.sh @@ -2,22 +2,29 @@ set -euo pipefail # Builds a dynamic GitHub Actions matrix for run-samples.yml. -# Usage: build-matrix.sh [base_sha] [arch_filter] +# Usage: build-matrix.sh [base_sha] [arch_filter] [platform] # "all" runs every test; "changed" only runs tests whose watch_folders have modified files RUN_MODE="${1:-all}" BASE_SHA="${2:-}" -# "both" runs each test on every architecture it supports; "amd64"/"arm64" restrict it to one +# "both" runs each test on every architecture it supports; "amd64"/"arm64" restrict it to one. It applies to +# the Linux entries: the Windows runner is amd64, so a Windows entry always runs on amd64 ARCH_FILTER="${3:-both}" case "$ARCH_FILTER" in both | amd64 | arm64) ;; *) echo "Invalid arch filter: '$ARCH_FILTER' (expected both, amd64 or arm64)" >&2; exit 1 ;; esac +# "Linux" or "Windows" picks the runners the tests run on, "all" both: one entry per test and platform +PLATFORM="${4:-Linux}" +case "$PLATFORM" in + Linux | Windows | all) ;; + *) echo "Invalid platform: '$PLATFORM' (expected Linux, Windows or all)" >&2; exit 1 ;; +esac # Get JSON metadata for all tests from run-samples.sh --list TEST_META=$(./run-samples.sh --list) TOTAL=$(echo "$TEST_META" | jq length) -echo "Run mode: $RUN_MODE | Arch filter: $ARCH_FILTER | Total tests: $TOTAL" +echo "Run mode: $RUN_MODE | Arch filter: $ARCH_FILTER | Platform: $PLATFORM | Total tests: $TOTAL" # Changes to these files affect all tests, so any modification triggers a full run INFRA_FILES="run-samples.sh Makefile .github/workflows/run-samples.yml .github/scripts/build-matrix.sh pyproject.toml requirements-dev.txt requirements-runtime.txt" @@ -62,23 +69,40 @@ if [[ -z "${INDICES:-}" ]]; then MATRIX='{"include":[]}' else # Convert space-separated indices to JSON array, then build the matrix object. - # Each selected test is expanded into one entry per architecture it supports, so a + # Each selected test is expanded into one Linux entry per architecture it supports, so a # test that runs on both arches becomes two independent jobs. Tests that declare only # amd64 depend on an amd64-only container image — see run-samples.sh for the details. + # A Windows entry runs the test from Git Bash on the amd64 Windows runner: every test + # declares amd64, so each selected test gets one. IDX_JSON=$(echo "$INDICES" | tr ' ' '\n' | jq -R 'tonumber' | jq -s '.') - MATRIX=$(echo "$TEST_META" | jq -c --argjson idx "$IDX_JSON" --arg filter "$ARCH_FILTER" \ - '{include: [ - $idx[] as $i | .[$i] as $test | - $test.arches[] | - select($filter == "both" or . == $filter) | - { - shard: $test.shard, - splits: $test.splits, - arch: ., - runner: (if . == "arm64" then "ubuntu-22.04-arm" else "ubuntu-22.04" end), - name: ($test.name + " [" + . + "]") - } - ]}') + MATRIX=$(echo "$TEST_META" | jq -c --argjson idx "$IDX_JSON" --arg filter "$ARCH_FILTER" --arg platform "$PLATFORM" \ + '{include: ( + (if $platform == "Windows" then [] else [ + $idx[] as $i | .[$i] as $test | + $test.arches[] | + select($filter == "both" or . == $filter) | + { + shard: $test.shard, + splits: $test.splits, + arch: ., + os: "linux", + runner: (if . == "arm64" then "ubuntu-22.04-arm" else "ubuntu-22.04" end), + name: ($test.name + " [" + . + "]") + } + ] end) + + (if $platform == "Linux" then [] else [ + $idx[] as $i | .[$i] as $test | + select($test.arches | index("amd64")) | + { + shard: $test.shard, + splits: $test.splits, + arch: "amd64", + os: "windows", + runner: "windows-latest", + name: ($test.name + " [Windows]") + } + ] end) + )}') fi # An arch filter can select tests that support no matching architecture, so decide diff --git a/.github/workflows/run-samples.yml b/.github/workflows/run-samples.yml index 34711d9..d0f6904 100644 --- a/.github/workflows/run-samples.yml +++ b/.github/workflows/run-samples.yml @@ -21,7 +21,7 @@ on: - changed default: changed arch: - description: "Architecture: 'both' runs every test on each architecture it supports" + description: "Architecture of the Linux jobs: 'both' runs every test on each architecture it supports; the Windows jobs always run on amd64" required: false type: choice options: @@ -29,6 +29,15 @@ on: - amd64 - arm64 default: both + platform: + description: "Where the selected tests run: Linux, Windows (from Git Bash with MSYS_NO_PATHCONV=1, on amd64), or all (both, in parallel jobs of the same run)" + required: false + type: choice + options: + - Windows + - Linux + - all + default: Linux # Defaults for pull_request events (change DEFAULT_RUN_MODE to 'changed' to only run affected tests) env: @@ -61,14 +70,18 @@ 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 }} + PLATFORM_INPUT: ${{ github.event.inputs.platform }} run: | - # Pick run mode and architecture: manual dispatch uses the dropdowns, PRs use the env defaults + # Pick run mode, architecture and platform: manual dispatch uses the dropdowns, PRs use the env + # defaults and run on Linux alone. if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then RUN_MODE="${RUN_MODE_INPUT}" ARCH="${ARCH_INPUT}" + PLATFORM="${PLATFORM_INPUT}" else RUN_MODE="${{ env.DEFAULT_RUN_MODE }}" ARCH="${{ env.DEFAULT_ARCH }}" + PLATFORM=Linux fi # In "changed" mode, determine the base commit to diff against @@ -81,13 +94,19 @@ jobs: fi fi - .github/scripts/build-matrix.sh "$RUN_MODE" "$BASE_SHA" "$ARCH" + .github/scripts/build-matrix.sh "$RUN_MODE" "$BASE_SHA" "$ARCH" "$PLATFORM" - # Each test runs in its own job — one matrix entry per test/architecture from the setup job + # Each test runs in its own job: one matrix entry per test and platform from the setup job, a Linux test on + # each architecture it supports. A Windows entry runs the sample from Git Bash with MSYS_NO_PATHCONV=1 set as + # the README asks Windows users to do. The emulator is a Linux image and Docker on the Windows runner runs Windows + # containers, so there 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. A step runs on both platforms unless its condition names one. 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 + if: needs.setup.outputs.has_tests == 'true' environment: AZURE strategy: fail-fast: false @@ -95,21 +114,27 @@ jobs: # A sample takes ~10-25 minutes; the bound covers a retry and stops a hung deployment from # holding a runner for GitHub's 6-hour default. timeout-minutes: 90 - # 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. + # ubuntu-22.04 for amd64, ubuntu-22.04-arm for arm64 and windows-latest for Windows: the matrix carries the + # label. GitHub-hosted arm64 runners are free for public repositories. runs-on: ${{ matrix.runner }} permissions: contents: read + defaults: + run: + # Git Bash on Windows, whose default shell is PowerShell + shell: bash + env: IMAGE_NAME: localstack/localstack-azure DEFAULT_TAG: latest # lstk finds the container daemon on its own, and lstk 0.20.0 picked the socket of a # co-installed Podman instead of Docker's, then reported the running emulator as absent # (fixed in 0.20.1, localstack/lstk#422). DOCKER_HOST always wins over that detection, - # so every lstk call in this job talks to the Docker daemon the emulator runs on. - DOCKER_HOST: unix:///var/run/docker.sock + # so every lstk call in this job talks to the Docker daemon the emulator runs on: on Windows, + # the Docker Engine in WSL 2, which the Windows docker CLI and lstk reach over TCP. + DOCKER_HOST: ${{ matrix.os == 'windows' && 'tcp://127.0.0.1:2375' || 'unix:///var/run/docker.sock' }} steps: - name: Checkout repo @@ -118,22 +143,26 @@ jobs: persist-credentials: false - name: Set up environment + if: matrix.os == 'linux' env: RUNNER_TEMP_DIR: ${{ runner.temp }} run: echo "AZURE_CONFIG_DIR=${RUNNER_TEMP_DIR}/azure-cli" >> "$GITHUB_ENV" - name: Set up Python + if: matrix.os == 'linux' uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: '3.12' cache: 'pip' - name: Set up .NET + if: matrix.os == 'linux' uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 with: dotnet-version: '10.0' - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + if: matrix.os == 'linux' with: distribution: 'temurin' java-version: '25' @@ -148,6 +177,7 @@ jobs: # postgresql-client: provides the `psql` CLI used by the PostgreSQL flexible-server and the # App Configuration samples to create the application role and seed the database schema # in the local emulator. + if: matrix.os == 'linux' run: | sudo apt-get update sudo apt-get install -y jq zip unixodbc-dev libsnappy-dev default-mysql-client postgresql-client @@ -160,9 +190,11 @@ jobs: # 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. + if: matrix.os == 'linux' run: sudo apt-get install -y --allow-downgrades azure-cli - name: Install Terraform + if: matrix.os == 'linux' uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 with: terraform_version: "1.5.0" @@ -171,6 +203,7 @@ jobs: - name: Install test dependencies # Mirroring the localstack-pro approach: install all Python dependencies # into a virtual environment to avoid system-level conflicts. + if: matrix.os == 'linux' run: make install - name: Install lstk CLI @@ -180,6 +213,7 @@ jobs: # 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. + if: matrix.os == 'linux' env: LSTK_VERSION: "1.4.0" run: | @@ -201,6 +235,7 @@ jobs: - 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. + if: matrix.os == 'linux' uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: ${{ secrets.DOCKERHUB_PULL_USERNAME }} @@ -209,6 +244,7 @@ jobs: - 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. + if: matrix.os == 'linux' run: | docker system prune -af --volumes docker builder prune -af @@ -216,6 +252,7 @@ jobs: - 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. + if: matrix.os == 'linux' run: docker pull ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} - name: Start LocalStack @@ -224,6 +261,7 @@ jobs: # code 55 - lstk prints the last lines of its output, so the reason shows up in this step. # A healthy emulator answers within seconds, so the generous timeout costs nothing on the # happy path. A start that fails or never becomes ready is retried on a fresh container. + if: matrix.os == 'linux' run: | attempts=3 for attempt in $(seq 1 "${attempts}"); do @@ -269,7 +307,7 @@ jobs: # 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' + if: matrix.os == 'linux' && 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 @@ -286,6 +324,7 @@ jobs: # from a provisioning call, a slow container start) fails the whole sample - which is what # has been needing manual job re-runs. The retry step below decides this job's outcome, so a # genuinely broken sample still fails: it fails both attempts. + if: matrix.os == 'linux' continue-on-error: true run: make test SHARD="${SHARD}" SPLITS="${SPLITS}" env: @@ -300,7 +339,7 @@ jobs: # retry runs against a fresh emulator instead of the half-provisioned state the failed # attempt left behind. run-samples.sh also discards the Terraform state that attempt left, # which no longer matches anything in the fresh emulator. - if: steps.initial_run.outcome == 'failure' + if: matrix.os == 'linux' && steps.initial_run.outcome == 'failure' run: | echo "::warning::${MATRIX_NAME} failed once; retrying on a fresh emulator." lstk stop --non-interactive || true @@ -331,10 +370,275 @@ jobs: # The Start and Retry steps keep a copy of every emulator's logs, so a retried job shows the # emulator that failed as well as the one that ran the retry, and a container that removed # itself on exit still has its logs. make logs covers a job where no emulator came up. - if: always() + if: always() && matrix.os == 'linux' run: | if [ -s "${RUNNER_TEMP}/localstack-azure.log" ]; then cat "${RUNNER_TEMP}/localstack-azure.log" else make logs fi + + - name: Set the Windows environment + # What the README asks Windows users of Git Bash and MSYS2 to export, the image the steps below start + # with the docker CLI, and the interpreter a virtual environment on Windows has: python.exe, no python3 + if: matrix.os == 'windows' + run: | + { + echo "MSYS_NO_PATHCONV=1" + echo "IMAGE=localstack/localstack-azure:latest" + echo "PYTHON_BIN=python" + } >> "$GITHUB_ENV" + + - 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. + if: matrix.os == 'windows' + 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 + # 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. + if: matrix.os == 'windows' + shell: pwsh + run: | + 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 + id: wsl + if: matrix.os == 'windows' + 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 + # 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. + if: matrix.os == 'windows' + 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 --host tcp://127.0.0.1:2375 info > /dev/null 2>&1 && break + sleep 2 + done + 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 + if: matrix.os == 'windows' + run: docker version + + - name: Login to Docker Hub + if: matrix.os == 'windows' + 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. The script stays in + # RUNNER_TEMP for the retry, which starts a fresh emulator the same way. + if: matrix.os == 'windows' + env: + LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} + GITHUB_API_TOKEN: ${{ github.token }} + run: | + 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 \ + --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" || 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" + 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 + 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. + if: matrix.os == 'windows' + 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 + if: matrix.os == 'windows' + run: npm install -g @localstack/lstk@1.4.0 + + - name: Set up Python + if: matrix.os == 'windows' + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3.12' + + - name: Set up .NET + if: matrix.os == 'windows' + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0 + with: + dotnet-version: '10.0' + + - name: Set up Java + # The Service Bus sample builds its Java app with Maven, which the runner image ships + if: matrix.os == 'windows' + 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 + if: matrix.os == 'windows' + uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: "1.5.0" + terraform_wrapper: false + + - 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. + if: matrix.os == 'windows' + 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. + if: matrix.os == 'windows' + env: + SQLCMD_VERSION: v1.10.0 + SQLCMD_SHA256: a4c28332fcc6e497d655e53ac8f4939f4ab170f9cfd32d0fa5081b60f4d9d691 + run: | + 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 + if: matrix.os == 'windows' + run: | + zip -v | sed -n 2p + psql --version + mysql --version + sqlcmd --version + java -version 2>&1 | 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"' + jq --version + unzip -v | sed -n 1p + + - name: Install the Python dependencies + if: matrix.os == 'windows' + 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.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: windows_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. + if: matrix.os == 'windows' + continue-on-error: true + env: + SHARD: ${{ matrix.shard }} + SPLITS: ${{ matrix.splits }} + LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }} + run: | + 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: matrix.os == 'windows' && steps.windows_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() && matrix.os == 'windows' + 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 + if: failure() && matrix.os == 'windows' && steps.wsl.outcome == 'success' + shell: wsl-bash {0} + run: tail -n 100 /var/log/dockerd.log || true diff --git a/run-samples.sh b/run-samples.sh index 8556020..9031c74 100755 --- a/run-samples.sh +++ b/run-samples.sh @@ -80,6 +80,8 @@ TERRAFORM_SAMPLES=( "samples/web-app-postgresql-flexible-server/dotnet/terraform|bash deploy.sh" "samples/web-app-app-configuration/python/terraform|bash deploy.sh" "samples/web-app-app-configuration/dotnet/terraform|bash deploy.sh" + "samples/web-app-custom-image/python/terraform|bash deploy.sh|bash ../scripts/validate.sh && bash ../scripts/call-web-app.sh" + "samples/web-app-custom-image/dotnet/terraform|bash deploy.sh|bash ../scripts/validate.sh && bash ../scripts/call-web-app.sh" "samples/aci-blob-storage/python/terraform|bash deploy.sh" "samples/container-apps-blob-storage/python/terraform|bash deploy.sh" "samples/container-apps-blob-storage/python/terraform|MANAGED_IDENTITY_TYPE=SystemAssigned bash deploy.sh" @@ -108,6 +110,8 @@ BICEP_SAMPLES=( "samples/web-app-postgresql-flexible-server/dotnet/bicep|bash deploy.sh" "samples/web-app-app-configuration/python/bicep|bash deploy.sh" "samples/web-app-app-configuration/dotnet/bicep|bash deploy.sh" + "samples/web-app-custom-image/python/bicep|bash deploy.sh|bash ../scripts/validate.sh && bash ../scripts/call-web-app.sh" + "samples/web-app-custom-image/dotnet/bicep|bash deploy.sh|bash ../scripts/validate.sh && bash ../scripts/call-web-app.sh" "samples/aci-blob-storage/python/bicep|bash deploy.sh" "samples/container-apps-blob-storage/python/bicep|bash deploy.sh" "samples/container-apps-blob-storage/python/bicep|MANAGED_IDENTITY_TYPE=SystemAssigned bash deploy.sh" diff --git a/samples/web-app-custom-image/dotnet/terraform/main.tf b/samples/web-app-custom-image/dotnet/terraform/main.tf index ae6ad62..8de1402 100644 --- a/samples/web-app-custom-image/dotnet/terraform/main.tf +++ b/samples/web-app-custom-image/dotnet/terraform/main.tf @@ -177,7 +177,9 @@ resource "null_resource" "push_image" { count = (var.image_name != null && var.image_name != "" && var.image_tag != null && var.image_tag != "") ? 1 : 0 provisioner "local-exec" { - command = "${path.root}/push_image.sh" + # Run the script with bash everywhere: on Windows, Terraform would otherwise hand it to cmd.exe, which cannot run it + command = "${path.root}/push_image.sh" + interpreter = ["bash"] environment = { ACR_NAME = local.acr_name ACR_LOGIN_SERVER = module.container_registry.login_server diff --git a/samples/web-app-custom-image/python/terraform/main.tf b/samples/web-app-custom-image/python/terraform/main.tf index ae6ad62..8de1402 100644 --- a/samples/web-app-custom-image/python/terraform/main.tf +++ b/samples/web-app-custom-image/python/terraform/main.tf @@ -177,7 +177,9 @@ resource "null_resource" "push_image" { count = (var.image_name != null && var.image_name != "" && var.image_tag != null && var.image_tag != "") ? 1 : 0 provisioner "local-exec" { - command = "${path.root}/push_image.sh" + # Run the script with bash everywhere: on Windows, Terraform would otherwise hand it to cmd.exe, which cannot run it + command = "${path.root}/push_image.sh" + interpreter = ["bash"] environment = { ACR_NAME = local.acr_name ACR_LOGIN_SERVER = module.container_registry.login_server