Repository navigation
644 lines (591 loc) · 30.9 KB
/
Copy pathrun-samples.yml
File metadata and controls
644 lines (591 loc) · 30.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
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 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:
- both
- 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:
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 }}
PLATFORM_INPUT: ${{ github.event.inputs.platform }}
run: |
# 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
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" "$PLATFORM"
# 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
# Skip entirely when no tests match
if: needs.setup.outputs.has_tests == 'true'
environment: AZURE
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
# 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 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: 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
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
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'
- 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 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
find . -name "*.sh" -exec chmod +x {} +
- name: Install Azure CLI
# 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.
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"
terraform_wrapper: false
- 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
# Starts and stops the emulator, and lets run-samples.sh route Azure CLI calls to it
# via `lstk az start-interception`. 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.
if: matrix.os == 'linux'
env:
LSTK_VERSION: "1.4.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: 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 }}
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.
if: matrix.os == 'linux'
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.
if: matrix.os == 'linux'
run: docker pull ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}
- name: Start LocalStack
# lstk starts the emulator and returns once its health endpoint answers, or fails when the
# timeout expires. If the container exits while starting - a rejected auth token exits with
# 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
if lstk start --non-interactive --type azure --image "${IMAGE}" --timeout 400s; then
echo "Emulator ready (attempt ${attempt}/${attempts})."
# The container removes itself, logs included, when it exits. Keep a copy for the
# Get LocalStack Logs step in case the emulator dies while the sample runs.
nohup docker logs --follow localstack-azure >> "${RUNNER_TEMP}/localstack-azure.log" 2>&1 < /dev/null &
exit 0
fi
echo "::warning::Emulator failed to start or was not ready within 400s (attempt ${attempt}/${attempts})."
# A timed-out start leaves the container running; one that exited is already gone.
docker logs localstack-azure --tail 100 2>&1 || true
lstk stop --non-interactive || true
# stop does not wait for Docker to remove the container, and lstk start reports a container
# that is still running as started, ready or not. Remove it and wait for the name to free up,
# so the next attempt boots a fresh emulator.
docker rm -f localstack-azure >/dev/null 2>&1 || true
for _ in $(seq 1 30); do docker container inspect localstack-azure >/dev/null 2>&1 || break; sleep 1; done
done
echo "::error::Emulator failed to become ready after ${attempts} attempts."
exit 1
env:
IMAGE: ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}
LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }}
# lstk forwards LOCALSTACK_-prefixed variables to the emulator container, and the
# image's entrypoint strips the prefix: LOCALSTACK_LS_LOG reaches the runtime as LS_LOG.
# lstk also reads LOCALSTACK_DISABLE_EVENTS itself and sends no telemetry of its own.
# GITHUB_API_TOKEN: the emulator's package installers query api.github.com
# (e.g. Azure Functions Core Tools releases when prebuilding the Functions
# image); anonymous calls share the runner egress IP's 60 req/h budget,
# which is routinely exhausted on GitHub-hosted runners. localstack-core's
# GitHubReleaseInstaller sends this token as a Bearer for the higher
# authenticated limit. The workflow token is ephemeral and contents:read.
LOCALSTACK_GITHUB_API_TOKEN: ${{ github.token }}
LOCALSTACK_MSSQL_ACCEPT_EULA: "Y"
LOCALSTACK_LS_LOG: "DEBUG"
LOCALSTACK_DISABLE_EVENTS: "1"
LOCALSTACK_DNS_ADDRESS: "0"
- 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.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
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.
id: initial_run
# Samples provision real containers through the emulator, so a single transient error (a 500
# 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:
# 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: "Retry: ${{ matrix.name }}"
# The deploy scripts are not idempotent - they fail on resources that already exist - so the
# 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: 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
# stop does not wait for Docker to remove the container, and lstk start reports a container
# that is still running as started, ready or not. Remove it and wait for the name to free up,
# so the retry boots a fresh emulator.
docker rm -f localstack-azure >/dev/null 2>&1 || true
for _ in $(seq 1 30); do docker container inspect localstack-azure >/dev/null 2>&1 || break; sleep 1; done
lstk start --non-interactive --type azure --image "${IMAGE}" --timeout 600s
echo "===== Emulator restarted for the retry =====" >> "${RUNNER_TEMP}/localstack-azure.log"
nohup docker logs --follow localstack-azure >> "${RUNNER_TEMP}/localstack-azure.log" 2>&1 < /dev/null &
make test SHARD="${SHARD}" SPLITS="${SPLITS}"
env:
MATRIX_NAME: ${{ matrix.name }}
SHARD: ${{ matrix.shard }}
SPLITS: ${{ matrix.splits }}
IMAGE: ${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}
LOCALSTACK_AUTH_TOKEN: ${{ secrets.TEST_LOCALSTACK_AUTH_TOKEN }}
LOCALSTACK_GITHUB_API_TOKEN: ${{ github.token }}
LOCALSTACK_MSSQL_ACCEPT_EULA: "Y"
LOCALSTACK_LS_LOG: "DEBUG"
LOCALSTACK_DISABLE_EVENTS: "1"
LOCALSTACK_DNS_ADDRESS: "0"
PURGE_DOCKER: "1"
- name: Get LocalStack Logs
# Captured on failure or success to provide a detailed audit trail of the emulator's activity.
# 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() && 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 <JSON>`, 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