diff --git a/README.md b/README.md index 472c99f..ebe15e8 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,44 @@ find . -name "*.sh" -exec sed -i 's/\r$//' {} + ``` Or by installing and using `dos2unix`. +### Windows: Git Bash and MSYS2 + +The scripts are written for Bash and run unchanged on Linux, macOS and WSL. +They also run on Windows from Git Bash or from an MSYS2 shell (for example MINGW64 or UCRT64), provided you first turn off the automatic path conversion of these shells and make them drop the carriage returns Windows programs print: + +```bash +export MSYS_NO_PATHCONV=1 +set -o igncr +export SHELLOPTS +``` + +When Git Bash or an MSYS2 shell starts a Windows program such as the Azure CLI, it rewrites every argument that looks like an absolute POSIX path into a Windows path. +Azure values that only look like paths are rewritten too, and the commands that receive them fail: + +- `--partition-key-path /username` reaches `az` as `C:/Program Files/Git/username`, so creating the Cosmos DB container fails with an invalid partition key path error. +- Resource IDs such as `/subscriptions//resourceGroups/...`, passed to `--scope`, `--ids` and similar options, become paths under the shell's installation folder, for example `C:/Program Files/Git/subscriptions/...`. +- Paths such as the Front Door `--origin-path /api` are rewritten the same way. + +With `MSYS_NO_PATHCONV=1` set, these values reach the Azure CLI exactly as written. +The scripts convert the paths of the local files they hand to Windows programs, such as the temporary files read by `curl` and `jq`, with `cygpath` when it is available, so they keep working with the variable set. +They also hand `curl` the Windows `NUL` device instead of `/dev/null` for the responses they discard, because the variable stops these shells from making that conversion too. +On Linux and macOS `cygpath` does not exist, and the paths are left unchanged. + +Windows programs such as the Azure CLI and `jq` also end every line they print with a carriage return, which Bash keeps when a script captures their output with `$(...)` or `read`. +A name or an ID read from `az ... --output tsv` then carries an invisible `\r`, and the next command that receives it fails, for example with `ResourceNotFound` for a resource that exists. +`set -o igncr` makes these shells drop carriage returns from `$(...)` and `read`, and `export SHELLOPTS` passes the option on to the scripts you start. +`mapfile` keeps them even then, so the scripts that fill an array from the Azure CLI strip them with `tr -d '\r'`. + +Tips: + +- To apply these settings in every new Git Bash window, add the three lines to `~/.bash_profile`, or to `~/.bashrc` if your `.bash_profile` sources it. +- `echo $MSYSTEM` prints a value such as `MINGW64` only in these shells. +- Any value turns the conversion off, `0` included; run `unset MSYS_NO_PATHCONV` to turn it back on, and `set +o igncr` to keep carriage returns again. +- WSL runs Linux and needs none of these settings. +- When you write a script that passes a local file to `az`, `curl`, `jq`, `python`, `docker` or `zip`, convert its path with the `native_path` helper the existing scripts define; Git Bash ships no `zip`, and a `zip.exe` built for Windows needs the converted path too. +- To discard what a Windows program writes to a file, such as `curl -o`, pass it `"$(null_device)"` rather than `/dev/null`, as the existing scripts do. Shell redirections such as `> /dev/null` need no change. +- A launcher written as a shell script that hands its own paths to a Windows program, such as Maven's `mvn`, needs the conversion: run it as `(unset MSYS_NO_PATHCONV; mvn ...)`, as the Service Bus sample does. Setting the variable to an empty value is not enough, because any value turns the conversion off. + ## Configuration Follow the comprehensive setup guide in [LocalStack for Azure Quick Start](./docs/LOCALSTACK.md) to configure your LocalStack for Azure development environment. diff --git a/run-samples.sh b/run-samples.sh index 4cf1569..f78b461 100755 --- a/run-samples.sh +++ b/run-samples.sh @@ -38,6 +38,7 @@ SAMPLES=( "samples/function-app-storage-http/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-http-triggers.sh" "samples/web-app-cosmosdb-mongodb-api/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh" "samples/web-app-cosmosdb-mongodb-api/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh" + "samples/web-app-cosmosdb-nosql-api/python|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh" "samples/web-app-cosmosdb-nosql-api/dotnet|bash scripts/deploy.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh" "samples/web-app-managed-identity/python|bash scripts/user-assigned.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh" "samples/web-app-managed-identity/dotnet|bash scripts/user-assigned.sh|bash scripts/validate.sh && bash scripts/call-web-app.sh" diff --git a/samples/api-management-function-app/python/scripts/call-api.sh b/samples/api-management-function-app/python/scripts/call-api.sh index 355a027..96ca401 100755 --- a/samples/api-management-function-app/python/scripts/call-api.sh +++ b/samples/api-management-function-app/python/scripts/call-api.sh @@ -11,9 +11,21 @@ APIM_NAME="${PREFIX}-inventory-apim-${SUFFIX}" APIM_API_VERSION='2022-08-01' API_PATH='inventory' APIM_SUBSCRIPTION_ID='partner-subscription' + +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + # mktemp rather than fixed paths, as in validate.sh: parallel-safe, and cleaned up on exit. -BODY_FILE="$(mktemp "${TMPDIR:-/tmp}/inventory_call.XXXXXX.json")" -HEADERS_FILE="$(mktemp "${TMPDIR:-/tmp}/inventory_call_headers.XXXXXX.txt")" +BODY_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/inventory_call.XXXXXX.json")")" +HEADERS_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/inventory_call_headers.XXXXXX.txt")")" trap 'rm -f "$BODY_FILE" "$HEADERS_FILE"' EXIT # Retrieve the API Management service diff --git a/samples/api-management-function-app/python/scripts/deploy.sh b/samples/api-management-function-app/python/scripts/deploy.sh index 99c085b..4f7f71e 100755 --- a/samples/api-management-function-app/python/scripts/deploy.sh +++ b/samples/api-management-function-app/python/scripts/deploy.sh @@ -29,7 +29,19 @@ APIM_SUBSCRIPTION_ID='partner-subscription' NAMED_VALUE_ID='backend-secret' FUNCTION_ZIPFILE='inventory_function.zip' CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" -APIM_DIR="$CURRENT_DIR/../apim" + +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + +APIM_DIR="$(native_path "$CURRENT_DIR/../apim")" # Get the current subscription SUBSCRIPTION_NAME=$(az account show --query name --output tsv) diff --git a/samples/api-management-function-app/python/scripts/validate.sh b/samples/api-management-function-app/python/scripts/validate.sh index e6025e2..1fa3e45 100755 --- a/samples/api-management-function-app/python/scripts/validate.sh +++ b/samples/api-management-function-app/python/scripts/validate.sh @@ -14,10 +14,33 @@ API_ID='inventory-api' API_PATH='inventory' APIM_SUBSCRIPTION_ID='partner-subscription' RATE_LIMIT_CALLS=10 + +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # mktemp rather than fixed paths: two runs of this script must not write the same file, and # `curl -o` would follow a symlink a third party had left at a predictable name. -BODY_FILE="$(mktemp "${TMPDIR:-/tmp}/inventory_body.XXXXXX.json")" -HEADERS_FILE="$(mktemp "${TMPDIR:-/tmp}/inventory_headers.XXXXXX.txt")" +BODY_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/inventory_body.XXXXXX.json")")" +HEADERS_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/inventory_headers.XXXXXX.txt")")" trap 'rm -f "$BODY_FILE" "$HEADERS_FILE"' EXIT FAILED=0 @@ -54,7 +77,7 @@ echo "Gateway URL: $API_URL" echo "Waiting for the [$FUNCTION_APP_NAME] function app to answer..." BACKEND_STATUS="" for i in $(seq 1 60); do - BACKEND_STATUS=$(curl -s -m 10 -o /dev/null -w "%{http_code}" "$BACKEND_URL/items") + BACKEND_STATUS=$(curl -s -m 10 -o "$(null_device)" -w "%{http_code}" "$BACKEND_URL/items") [[ "$BACKEND_STATUS" == "401" ]] && break sleep 5 done @@ -211,7 +234,7 @@ fi # so the preflight never reaches the API's cors policy. Asserted against Azure only. # TODO: assert this unconditionally once the emulator lets the gateway's cors policy answer. echo "Sending a CORS preflight to [$API_URL/items]..." -PREFLIGHT_STATUS=$(curl -s -m 10 -o /dev/null -D "$HEADERS_FILE" -w "%{http_code}" \ +PREFLIGHT_STATUS=$(curl -s -m 10 -o "$(null_device)" -D "$HEADERS_FILE" -w "%{http_code}" \ -X OPTIONS \ -H "Origin: http://localhost:3000" \ -H "Access-Control-Request-Method: GET" \ diff --git a/samples/container-apps-blob-storage/python/scripts/validate.sh b/samples/container-apps-blob-storage/python/scripts/validate.sh index 4017672..3028cef 100644 --- a/samples/container-apps-blob-storage/python/scripts/validate.sh +++ b/samples/container-apps-blob-storage/python/scripts/validate.sh @@ -8,6 +8,17 @@ # replicas, the live HTTP ingress, and a revision update. # ============================================================================= +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables (must match deploy.sh) PREFIX='local' LOCATION='eastus' @@ -232,7 +243,7 @@ echo "[12] Guestbook Round Trip" ENTRY_AUTHOR="validate-sh" ENTRY_MESSAGE="Hello from validate.sh at $(date +%s)" echo -n " Posting a guestbook entry... " -HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ +HTTP_CODE=$(curl -s -o "$(null_device)" -w "%{http_code}" --max-time 10 \ -X POST "$APP_URL/" \ --data-urlencode "author=$ENTRY_AUTHOR" \ --data-urlencode "message=$ENTRY_MESSAGE" 2>/dev/null) diff --git a/samples/eventhubs-eventgrid/python/bicep/deploy.sh b/samples/eventhubs-eventgrid/python/bicep/deploy.sh index 48a0cf5..33f6d68 100644 --- a/samples/eventhubs-eventgrid/python/bicep/deploy.sh +++ b/samples/eventhubs-eventgrid/python/bicep/deploy.sh @@ -14,6 +14,17 @@ FUNCTION_ZIP='capture_processor.zip' CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + cd "$CURRENT_DIR" || exit 1 fail() { @@ -120,7 +131,7 @@ echo "=== Publishing the application code ===" # ----------------------------------------------------------------------------- cd "$SRC_DIR/functions" || fail "missing src/functions" rm -f "$CURRENT_DIR/$FUNCTION_ZIP" -zip -r "$CURRENT_DIR/$FUNCTION_ZIP" function_app.py host.json requirements.txt 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$FUNCTION_ZIP")" function_app.py host.json requirements.txt 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the capture processor (the first run can take several minutes)..." diff --git a/samples/eventhubs-eventgrid/python/scripts/deploy.sh b/samples/eventhubs-eventgrid/python/scripts/deploy.sh index fa9447e..34c8722 100644 --- a/samples/eventhubs-eventgrid/python/scripts/deploy.sh +++ b/samples/eventhubs-eventgrid/python/scripts/deploy.sh @@ -51,6 +51,17 @@ FUNCTION_ZIP='capture_processor.zip' CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + cd "$CURRENT_DIR" || exit 1 fail() { @@ -314,7 +325,7 @@ echo "Configured function app settings." cd "$SRC_DIR/functions" || fail "missing src/functions" rm -f "$CURRENT_DIR/$FUNCTION_ZIP" -zip -r "$CURRENT_DIR/$FUNCTION_ZIP" function_app.py host.json requirements.txt 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$FUNCTION_ZIP")" function_app.py host.json requirements.txt 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the processor (the first run pulls the build image and can take a few minutes)..." diff --git a/samples/eventhubs-eventgrid/python/scripts/run-pipeline.sh b/samples/eventhubs-eventgrid/python/scripts/run-pipeline.sh index ca569fa..b71e52d 100644 --- a/samples/eventhubs-eventgrid/python/scripts/run-pipeline.sh +++ b/samples/eventhubs-eventgrid/python/scripts/run-pipeline.sh @@ -20,6 +20,17 @@ CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" PYTHON_BIN="${PYTHON_BIN:-python3}" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + READING_COUNT="${READING_COUNT:-120}" DEVICE_COUNT="${DEVICE_COUNT:-6}" EXCURSION_DEVICE="${EXCURSION_DEVICE:-DEV-0003}" @@ -123,7 +134,7 @@ step "Step 3/5 - Event Hubs raises CaptureFileCreated, Event Grid delivers it" echo "Reading '$NOTIFICATION_HUB_NAME', where the subscription's EventHub destination delivers..." NOTIFICATIONS=$(EVENTHUB_LISTEN_CONNECTION_STRING="$EVENTHUB_LISTEN_CONNECTION_STRING" \ EVENT_HUB_NAME="$NOTIFICATION_HUB_NAME" \ - "$PYTHON_BIN" "$CURRENT_DIR/roundtrip_check.py" --partitions 2>/dev/null | tail -1) + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/roundtrip_check.py")" --partitions 2>/dev/null | tail -1) echo " $NOTIFICATIONS" echo "" echo "Nothing polled Blob Storage and no webhook was exposed: the notification arrived as a" @@ -138,7 +149,7 @@ SUMMARY_OUTPUT="" while [ $SECONDS -lt $DEADLINE ]; do SUMMARY_OUTPUT=$(EVENTHUB_LISTEN_CONNECTION_STRING="$EVENTHUB_LISTEN_CONNECTION_STRING" \ CURATED_HUB_NAME="$CURATED_HUB_NAME" \ - "$PYTHON_BIN" "$CURRENT_DIR/read_curated.py" 2>/dev/null) + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/read_curated.py")" 2>/dev/null) if [[ "$SUMMARY_OUTPUT" == SUMMARIES_OK* ]]; then break fi diff --git a/samples/eventhubs-eventgrid/python/scripts/validate.sh b/samples/eventhubs-eventgrid/python/scripts/validate.sh index 50ea08d..3a8acb4 100644 --- a/samples/eventhubs-eventgrid/python/scripts/validate.sh +++ b/samples/eventhubs-eventgrid/python/scripts/validate.sh @@ -26,6 +26,17 @@ TELEMETRY_PARTITION_COUNT=4 CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" PYTHON_BIN="${PYTHON_BIN:-python3}" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + PASS_COUNT=0 FAIL_COUNT=0 @@ -158,7 +169,7 @@ ROUNDTRIP=$(EVENTHUB_SEND_CONNECTION_STRING=$(az eventhubs eventhub authorizatio --resource-group "$RESOURCE_GROUP_NAME" \ --query primaryConnectionString -o tsv --only-show-errors) \ EVENT_HUB_NAME="$TELEMETRY_HUB_NAME" \ - "$PYTHON_BIN" "$CURRENT_DIR/roundtrip_check.py" 2>&1 | tail -1) + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/roundtrip_check.py")" 2>&1 | tail -1) if [[ "$ROUNDTRIP" == ROUNDTRIP_OK* ]]; then echo "OK (${ROUNDTRIP#ROUNDTRIP_OK })" PASS_COUNT=$((PASS_COUNT + 1)) diff --git a/samples/eventhubs-eventgrid/python/terraform/deploy.sh b/samples/eventhubs-eventgrid/python/terraform/deploy.sh index bfedf15..46d702d 100644 --- a/samples/eventhubs-eventgrid/python/terraform/deploy.sh +++ b/samples/eventhubs-eventgrid/python/terraform/deploy.sh @@ -10,6 +10,17 @@ CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" FUNCTION_ZIP='capture_processor.zip' +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + cd "$CURRENT_DIR" || exit 1 fail() { @@ -60,7 +71,7 @@ FUNCTION_APP_NAME=$(terraform output -raw function_app_name) cd "$SRC_DIR/functions" || fail "missing src/functions" rm -f "$CURRENT_DIR/$FUNCTION_ZIP" -zip -r "$CURRENT_DIR/$FUNCTION_ZIP" function_app.py host.json requirements.txt 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$FUNCTION_ZIP")" function_app.py host.json requirements.txt 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the capture processor (the first run can take several minutes)..." diff --git a/samples/eventhubs/python/bicep/deploy.sh b/samples/eventhubs/python/bicep/deploy.sh index 2232205..84567f1 100644 --- a/samples/eventhubs/python/bicep/deploy.sh +++ b/samples/eventhubs/python/bicep/deploy.sh @@ -16,6 +16,17 @@ WEB_APP_ZIP="dashboard.zip" CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + cd "$CURRENT_DIR" || exit 1 fail() { @@ -142,7 +153,7 @@ echo "=== Publishing the application code ===" # ----------------------------------------------------------------------------- cd "$SRC_DIR/functions" || fail "missing src/functions" rm -f "$CURRENT_DIR/$FUNCTION_ZIP" -zip -r "$CURRENT_DIR/$FUNCTION_ZIP" function_app.py host.json requirements.txt 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$FUNCTION_ZIP")" function_app.py host.json requirements.txt 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the fraud detection function (the first run can take several minutes)..." @@ -155,7 +166,7 @@ rm -f "$FUNCTION_ZIP" cd "$SRC_DIR/dashboard" || fail "missing src/dashboard" rm -f "$CURRENT_DIR/$WEB_APP_ZIP" -zip -r "$CURRENT_DIR/$WEB_APP_ZIP" app.py gunicorn.conf.py requirements.txt templates static 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$WEB_APP_ZIP")" app.py gunicorn.conf.py requirements.txt templates static 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the dashboard..." diff --git a/samples/eventhubs/python/scripts/deploy.sh b/samples/eventhubs/python/scripts/deploy.sh index 3c87a9a..fe4d3dc 100644 --- a/samples/eventhubs/python/scripts/deploy.sh +++ b/samples/eventhubs/python/scripts/deploy.sh @@ -68,6 +68,17 @@ FRAUD_VELOCITY_WINDOW_SECONDS=60 CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + cd "$CURRENT_DIR" || exit 1 fail() { @@ -455,7 +466,7 @@ echo "Configured function app settings." cd "$SRC_DIR/functions" || fail "missing src/functions" rm -f "$CURRENT_DIR/$FUNCTION_ZIP" -zip -r "$CURRENT_DIR/$FUNCTION_ZIP" function_app.py host.json requirements.txt 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$FUNCTION_ZIP")" function_app.py host.json requirements.txt 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the function package (first run pulls the build image and can take a few minutes)..." @@ -517,7 +528,7 @@ az webapp config set \ cd "$SRC_DIR/dashboard" || fail "missing src/dashboard" rm -f "$CURRENT_DIR/$WEB_APP_ZIP" -zip -r "$CURRENT_DIR/$WEB_APP_ZIP" app.py gunicorn.conf.py requirements.txt templates static 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$WEB_APP_ZIP")" app.py gunicorn.conf.py requirements.txt templates static 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the dashboard package..." diff --git a/samples/eventhubs/python/scripts/run-pipeline.sh b/samples/eventhubs/python/scripts/run-pipeline.sh index f105b61..2f0d49e 100644 --- a/samples/eventhubs/python/scripts/run-pipeline.sh +++ b/samples/eventhubs/python/scripts/run-pipeline.sh @@ -21,6 +21,17 @@ CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" PYTHON_BIN="${PYTHON_BIN:-python3}" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + # Capture flushes on the interval configured in deploy.sh (60s); allow for the scan cycle. CAPTURE_WAIT_SECONDS="${CAPTURE_WAIT_SECONDS:-150}" PROCESSING_WAIT_SECONDS="${PROCESSING_WAIT_SECONDS:-420}" @@ -85,7 +96,7 @@ fail() { count_events() { EVENTHUB_LISTEN_CONNECTION_STRING="$EVENTHUB_NAMESPACE_CONNECTION_STRING" \ EVENT_HUB_NAME="$1" \ - "$PYTHON_BIN" "$CURRENT_DIR/roundtrip_check.py" --partitions 2>/dev/null | tail -1 + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/roundtrip_check.py")" --partitions 2>/dev/null | tail -1 } # Number of events in a hub, as a bare integer. Returns non-zero when the hub cannot be @@ -94,7 +105,7 @@ count_total() { local total total=$(EVENTHUB_LISTEN_CONNECTION_STRING="$EVENTHUB_NAMESPACE_CONNECTION_STRING" \ EVENT_HUB_NAME="$1" \ - "$PYTHON_BIN" "$CURRENT_DIR/roundtrip_check.py" --total 2>/dev/null | tail -1) + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/roundtrip_check.py")" --total 2>/dev/null | tail -1) [[ "$total" =~ ^[0-9]+$ ]] || return 1 echo "$total" } @@ -204,7 +215,7 @@ echo "Alerts before restart: $ALERTS_BEFORE" echo "Restarting the processor by redeploying the function package..." cd "$SRC_DIR/functions" || fail "missing src/functions" rm -f "$CURRENT_DIR/fraud_function.zip" -zip -r "$CURRENT_DIR/fraud_function.zip" function_app.py host.json requirements.txt 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/fraud_function.zip")" function_app.py host.json requirements.txt 1>/dev/null cd "$CURRENT_DIR" || fail "could not return to the scripts directory" az functionapp deploy \ --resource-group "$RESOURCE_GROUP_NAME" \ @@ -279,7 +290,7 @@ echo "Decoding the newest archive:" # which would hide a decoder that failed. DECODED=$(STORAGE_CONNECTION_STRING="$STORAGE_CONNECTION_STRING" \ CAPTURE_CONTAINER_NAME="$CAPTURE_CONTAINER_NAME" \ - "$PYTHON_BIN" "$CURRENT_DIR/read_capture.py" 2>&1) || + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/read_capture.py")" 2>&1) || fail "the newest Capture archive could not be decoded as Avro: $(echo "$DECODED" | tail -3)" echo "$DECODED" | sed 's/^/ /' diff --git a/samples/eventhubs/python/scripts/validate.sh b/samples/eventhubs/python/scripts/validate.sh index 204eb4b..bce103b 100644 --- a/samples/eventhubs/python/scripts/validate.sh +++ b/samples/eventhubs/python/scripts/validate.sh @@ -39,6 +39,17 @@ APPLICATION_GROUP_NAME='pos-terminals' CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" PYTHON_BIN="${PYTHON_BIN:-python3}" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + PASS_COUNT=0 FAIL_COUNT=0 @@ -190,7 +201,7 @@ echo -n " Checking an event survives a send/receive round trip... " ROUNDTRIP=$(EVENTHUB_SEND_CONNECTION_STRING="$SEND_CONNECTION" \ EVENTHUB_LISTEN_CONNECTION_STRING="$LISTEN_CONNECTION" \ EVENT_HUB_NAME="$EVENT_HUB_NAME" \ - "$PYTHON_BIN" "$CURRENT_DIR/roundtrip_check.py" 2>&1 | tail -1) + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/roundtrip_check.py")" 2>&1 | tail -1) if [[ "$ROUNDTRIP" == ROUNDTRIP_OK* ]]; then pass "${ROUNDTRIP#ROUNDTRIP_OK }" else @@ -202,7 +213,7 @@ echo "[9] Runtime information" echo -n " Checking partition runtime metadata is readable... " PARTITIONS=$(EVENTHUB_LISTEN_CONNECTION_STRING="$LISTEN_CONNECTION" \ EVENT_HUB_NAME="$EVENT_HUB_NAME" \ - "$PYTHON_BIN" "$CURRENT_DIR/roundtrip_check.py" --partitions 2>&1 | tail -1) + "$PYTHON_BIN" "$(native_path "$CURRENT_DIR/roundtrip_check.py")" --partitions 2>&1 | tail -1) if [[ "$PARTITIONS" == PARTITIONS_OK* ]]; then pass "${PARTITIONS#PARTITIONS_OK }" else diff --git a/samples/eventhubs/python/terraform/deploy.sh b/samples/eventhubs/python/terraform/deploy.sh index 88242d9..9c033eb 100644 --- a/samples/eventhubs/python/terraform/deploy.sh +++ b/samples/eventhubs/python/terraform/deploy.sh @@ -11,6 +11,17 @@ SRC_DIR="$(cd "$CURRENT_DIR/../src" && pwd)" FUNCTION_ZIP="fraud_function.zip" WEB_APP_ZIP="dashboard.zip" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + cd "$CURRENT_DIR" || exit 1 fail() { @@ -61,7 +72,7 @@ WEB_APP_NAME=$(terraform output -raw web_app_name) cd "$SRC_DIR/functions" || fail "missing src/functions" rm -f "$CURRENT_DIR/$FUNCTION_ZIP" -zip -r "$CURRENT_DIR/$FUNCTION_ZIP" function_app.py host.json requirements.txt 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$FUNCTION_ZIP")" function_app.py host.json requirements.txt 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the fraud detection function (the first run can take several minutes)..." @@ -74,7 +85,7 @@ rm -f "$FUNCTION_ZIP" cd "$SRC_DIR/dashboard" || fail "missing src/dashboard" rm -f "$CURRENT_DIR/$WEB_APP_ZIP" -zip -r "$CURRENT_DIR/$WEB_APP_ZIP" app.py gunicorn.conf.py requirements.txt templates static 1>/dev/null +zip -r "$(native_path "$CURRENT_DIR/$WEB_APP_ZIP")" app.py gunicorn.conf.py requirements.txt templates static 1>/dev/null cd "$CURRENT_DIR" || exit 1 echo "Deploying the dashboard..." diff --git a/samples/function-app-front-door/python/scripts/call-front-door.sh b/samples/function-app-front-door/python/scripts/call-front-door.sh index f7f3eea..c148047 100755 --- a/samples/function-app-front-door/python/scripts/call-front-door.sh +++ b/samples/function-app-front-door/python/scripts/call-front-door.sh @@ -6,8 +6,20 @@ SUFFIX="${SUFFIX:-test}" RESOURCE_GROUP_NAME="${PREFIX}-rg" PROFILE_NAME="${PREFIX}-catalog-afd-${SUFFIX}" ENDPOINT_NAME="${PREFIX}-catalog-${SUFFIX}" -BODY_FILE="$(mktemp "${TMPDIR:-/tmp}/front_door_call.XXXXXX")" -HEADERS_FILE="$(mktemp "${TMPDIR:-/tmp}/front_door_call_headers.XXXXXX")" + +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + +BODY_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/front_door_call.XXXXXX")")" +HEADERS_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/front_door_call_headers.XXXXXX")")" trap 'rm -f "$BODY_FILE" "$HEADERS_FILE"' EXIT # Retrieve the Front Door profile diff --git a/samples/function-app-front-door/python/scripts/validate.sh b/samples/function-app-front-door/python/scripts/validate.sh index 803fe8f..63b012c 100755 --- a/samples/function-app-front-door/python/scripts/validate.sh +++ b/samples/function-app-front-door/python/scripts/validate.sh @@ -8,8 +8,31 @@ PRIMARY_FUNCTION_APP_NAME="${PREFIX}-catalog-primary-${SUFFIX}" SECONDARY_FUNCTION_APP_NAME="${PREFIX}-catalog-secondary-${SUFFIX}" PROFILE_NAME="${PREFIX}-catalog-afd-${SUFFIX}" ENDPOINT_NAME="${PREFIX}-catalog-${SUFFIX}" -BODY_FILE="$(mktemp "${TMPDIR:-/tmp}/front_door_body.XXXXXX")" -HEADERS_FILE="$(mktemp "${TMPDIR:-/tmp}/front_door_headers.XXXXXX")" + +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + +BODY_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/front_door_body.XXXXXX")")" +HEADERS_FILE="$(native_path "$(mktemp "${TMPDIR:-/tmp}/front_door_headers.XXXXXX")")" trap 'rm -f "$BODY_FILE" "$HEADERS_FILE"' EXIT FAILED=0 @@ -82,7 +105,7 @@ if [[ $FAILED != 0 ]]; then fi # The origins answer HEAD as well as GET, which is what the health probe sends. -HEAD_STATUS=$(curl -s -m 10 -o /dev/null -w "%{http_code}" -I "$PRIMARY_URL/health") +HEAD_STATUS=$(curl -s -m 10 -o "$(null_device)" -w "%{http_code}" -I "$PRIMARY_URL/health") echo "HEAD on the probe path: HTTP $HEAD_STATUS" if [[ "$HEAD_STATUS" == "200" ]]; then echo "The origin answers the probe method Front Door uses" @@ -175,7 +198,7 @@ fi # 6. The UrlRedirect rule answers at the edge: the origin is never called. echo "Calling [$ENDPOINT_URL/legacy]..." -REDIRECT_STATUS=$(curl -s -m 20 -o /dev/null -D "$HEADERS_FILE" -w "%{http_code}" "$ENDPOINT_URL/legacy") +REDIRECT_STATUS=$(curl -s -m 20 -o "$(null_device)" -D "$HEADERS_FILE" -w "%{http_code}" "$ENDPOINT_URL/legacy") LOCATION=$(grep -i "^location:" "$HEADERS_FILE" | tr -d '\r' | awk '{print $2}') echo "HTTP $REDIRECT_STATUS -> ${LOCATION:-(no Location header)}" if [[ "$REDIRECT_STATUS" == "302" && "$LOCATION" == */status ]]; then @@ -208,7 +231,7 @@ echo "Purging /catalog/* so the cache starts empty..." purge_catalog echo "Requesting [$ENDPOINT_URL/catalog/3] twice..." -curl -s -m 20 -o /dev/null -D "$HEADERS_FILE" "$ENDPOINT_URL/catalog/3" +curl -s -m 20 -o "$(null_device)" -D "$HEADERS_FILE" "$ENDPOINT_URL/catalog/3" FIRST_CACHE=$(grep -i "^x-cache:" "$HEADERS_FILE" | tr -d '\r' | awk '{print $2}') if echo "$FIRST_CACHE" | grep -qi "hit"; then echo "Expected the first request after a purge to reach the origin (got: $FIRST_CACHE)" @@ -239,7 +262,7 @@ purge_catalog PURGED_CACHE='' for i in $(seq 1 $PROPAGATION_ATTEMPTS); do - curl -s -m 20 -o /dev/null -D "$HEADERS_FILE" "$ENDPOINT_URL/catalog/3" + curl -s -m 20 -o "$(null_device)" -D "$HEADERS_FILE" "$ENDPOINT_URL/catalog/3" PURGED_CACHE=$(grep -i "^x-cache:" "$HEADERS_FILE" | tr -d '\r' | awk '{print $2}') echo "$PURGED_CACHE" | grep -qi "hit" || break sleep 5 @@ -253,7 +276,7 @@ else fi # A response the origin marks no-store is never cached, however often it is asked for. -curl -s -m 20 -o /dev/null -D "$HEADERS_FILE" "$ENDPOINT_URL/whoami" +curl -s -m 20 -o "$(null_device)" -D "$HEADERS_FILE" "$ENDPOINT_URL/whoami" curl -s -m 20 -o "$BODY_FILE" -D "$HEADERS_FILE" "$ENDPOINT_URL/whoami" NOSTORE_CACHE=$(grep -i "^x-cache:" "$HEADERS_FILE" | tr -d '\r' | awk '{print $2}') echo "A no-store response: X-Cache: ${NOSTORE_CACHE:-(none)}" @@ -330,7 +353,7 @@ az afd endpoint update \ DISABLED_STATUS='' for i in $(seq 1 $PROPAGATION_ATTEMPTS); do - DISABLED_STATUS=$(curl -s -m 20 -o /dev/null -w "%{http_code}" "$ENDPOINT_URL/status") + DISABLED_STATUS=$(curl -s -m 20 -o "$(null_device)" -w "%{http_code}" "$ENDPOINT_URL/status") [[ "$DISABLED_STATUS" != "200" ]] && break sleep 5 done @@ -351,7 +374,7 @@ az afd endpoint update \ ENABLED_STATUS='' for i in $(seq 1 $PROPAGATION_ATTEMPTS); do - ENABLED_STATUS=$(curl -s -m 20 -o /dev/null -w "%{http_code}" "$ENDPOINT_URL/status") + ENABLED_STATUS=$(curl -s -m 20 -o "$(null_device)" -w "%{http_code}" "$ENDPOINT_URL/status") [[ "$ENABLED_STATUS" == "200" ]] && break sleep 5 done diff --git a/samples/function-app-managed-identity/python/scripts/test.sh b/samples/function-app-managed-identity/python/scripts/test.sh index e62c1ae..8a65dc6 100755 --- a/samples/function-app-managed-identity/python/scripts/test.sh +++ b/samples/function-app-managed-identity/python/scripts/test.sh @@ -10,6 +10,17 @@ STORAGE_ACCOUNT_NAME="${PREFIX}storage${SUFFIX}" CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" TEMP_OUTPUT_FILE="" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + cleanup() { if [ -n "$TEMP_OUTPUT_FILE" ] && [ -f "$TEMP_OUTPUT_FILE" ]; then rm -f "$TEMP_OUTPUT_FILE" @@ -111,6 +122,7 @@ for ((i=1; i<=n; i++)); do if [ "$BLOB_EXISTS" == "true" ]; then echo "Processed file [$BLOB_NAME] found in the [$OUTPUT_CONTAINER_NAME] container." TEMP_OUTPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/processed-output.XXXXXX")" || { echo "Failed to create temporary file." >&2; exit 1; } + TEMP_OUTPUT_FILE="$(native_path "$TEMP_OUTPUT_FILE")" az storage blob download \ --container-name "$OUTPUT_CONTAINER_NAME" \ diff --git a/samples/servicebus/java/bicep/deploy.sh b/samples/servicebus/java/bicep/deploy.sh index 079911c..bc8335d 100755 --- a/samples/servicebus/java/bicep/deploy.sh +++ b/samples/servicebus/java/bicep/deploy.sh @@ -131,6 +131,8 @@ else exit 1 fi -# Start the Java application +# Start the Java application. Maven's mvn launcher is a shell script that hands its own paths to Java, +# a Windows program on Windows, and relies on Git Bash and MSYS2 converting them, so it runs with that +# conversion back on even when MSYS_NO_PATHCONV is set, as the README asks. echo "Starting Java application..." -cd "$CURRENT_DIR/../app" && mvn clean spring-boot:run +cd "$CURRENT_DIR/../app" && (unset MSYS_NO_PATHCONV; mvn clean spring-boot:run) diff --git a/samples/servicebus/java/scripts/deploy.sh b/samples/servicebus/java/scripts/deploy.sh index b1daf46..7697b05 100755 --- a/samples/servicebus/java/scripts/deploy.sh +++ b/samples/servicebus/java/scripts/deploy.sh @@ -103,9 +103,11 @@ else exit 1 fi -# Start the Java application +# Start the Java application. Maven's mvn launcher is a shell script that hands its own paths to Java, +# a Windows program on Windows, and relies on Git Bash and MSYS2 converting them, so it runs with that +# conversion back on even when MSYS_NO_PATHCONV is set, as the README asks. echo "Starting Java application..." -cd "$CURRENT_DIR/../app" && mvn clean spring-boot:run +cd "$CURRENT_DIR/../app" && (unset MSYS_NO_PATHCONV; mvn clean spring-boot:run) # Optionally tear down all resources (uncomment to enable) # echo "Deleting resource group [$RESOURCE_GROUP_NAME]..." diff --git a/samples/servicebus/java/terraform/deploy.sh b/samples/servicebus/java/terraform/deploy.sh index e90cee4..4c9875a 100755 --- a/samples/servicebus/java/terraform/deploy.sh +++ b/samples/servicebus/java/terraform/deploy.sh @@ -63,7 +63,9 @@ else exit 1 fi -# Start the Java application +# Start the Java application. Maven's mvn launcher is a shell script that hands its own paths to Java, +# a Windows program on Windows, and relies on Git Bash and MSYS2 converting them, so it runs with that +# conversion back on even when MSYS_NO_PATHCONV is set, as the README asks. echo "Starting Java application..." -cd "$CURRENT_DIR/../app" && mvn clean spring-boot:run +cd "$CURRENT_DIR/../app" && (unset MSYS_NO_PATHCONV; mvn clean spring-boot:run) diff --git a/samples/url-shortener/python/scripts/call-web-app.sh b/samples/url-shortener/python/scripts/call-web-app.sh index 3e36745..bc4e393 100755 --- a/samples/url-shortener/python/scripts/call-web-app.sh +++ b/samples/url-shortener/python/scripts/call-web-app.sh @@ -1,5 +1,16 @@ #!/bin/bash +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables PREFIX='local' SUFFIX='test' @@ -24,7 +35,7 @@ WEB_APP_URL="http://$WEB_APP_HOSTNAME" # Call the home page echo "Calling the [$WEB_APP_NAME] web app home page at [$WEB_APP_URL]..." -HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$WEB_APP_URL/") +HTTP_STATUS=$(curl -s -o "$(null_device)" -w "%{http_code}" "$WEB_APP_URL/") if [[ "$HTTP_STATUS" == "200" ]]; then echo "Home page of the [$WEB_APP_NAME] web app successfully returned [$HTTP_STATUS]" @@ -48,7 +59,7 @@ else fi echo "Following the short link [$WEB_APP_URL/l/$CODE]..." -REDIRECT_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$WEB_APP_URL/l/$CODE") +REDIRECT_STATUS=$(curl -s -o "$(null_device)" -w "%{http_code}" "$WEB_APP_URL/l/$CODE") if [[ "$REDIRECT_STATUS" == "302" ]]; then echo "Short link [$CODE] successfully redirected with [$REDIRECT_STATUS]" diff --git a/samples/url-shortener/python/scripts/validate.sh b/samples/url-shortener/python/scripts/validate.sh index e594ab3..eda8689 100755 --- a/samples/url-shortener/python/scripts/validate.sh +++ b/samples/url-shortener/python/scripts/validate.sh @@ -12,6 +12,28 @@ POSTGRES_DB_NAME='clicks' CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" STATE_FILE="$CURRENT_DIR/.last_deploy.env" +# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs +# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which +# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. +native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + FAILED=0 # Load the values persisted by deploy.sh (PostgreSQL credentials and endpoint) @@ -117,9 +139,10 @@ fi # 5. Fetch the rendered QR code from the public blob container. QR_URL=$(curl -s "$WEB_APP_URL/api/links/$CLEAN_CODE" | jq -r .qr_url) echo "Fetching the QR code from [$QR_URL]..." -QR_CONTENT_TYPE=$(curl -sk -o /tmp/urlshort_qr.svg -w "%{content_type}" "$QR_URL") +QR_FILE="$(native_path /tmp/urlshort_qr.svg)" +QR_CONTENT_TYPE=$(curl -sk -o "$QR_FILE" -w "%{content_type}" "$QR_URL") echo "QR content type: $QR_CONTENT_TYPE" -if grep -q " %{redirect_url}\n" "$WEB_APP_URL/l/$CLEAN_CODE" -curl -s -o /dev/null -w "Second redirect: %{http_code} -> %{redirect_url}\n" "$WEB_APP_URL/l/$CLEAN_CODE" +curl -s -o "$(null_device)" -w "First redirect: %{http_code} -> %{redirect_url}\n" "$WEB_APP_URL/l/$CLEAN_CODE" +curl -s -o "$(null_device)" -w "Second redirect: %{http_code} -> %{redirect_url}\n" "$WEB_APP_URL/l/$CLEAN_CODE" sleep 3 CLICKS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ --host="$POSTGRES_HOST" \ diff --git a/samples/web-app-app-configuration/dotnet/scripts/deploy.sh b/samples/web-app-app-configuration/dotnet/scripts/deploy.sh index 7ff5163..f152df7 100755 --- a/samples/web-app-app-configuration/dotnet/scripts/deploy.sh +++ b/samples/web-app-app-configuration/dotnet/scripts/deploy.sh @@ -459,12 +459,13 @@ fi # Retrieve the clientId, principalId and resource id of the user-assigned managed identity echo "Retrieving the clientId, principalId and resource id of the [$MANAGED_IDENTITY_NAME] managed identity..." +# tr drops the carriage return the Azure CLI ends each line with on Windows: mapfile keeps it, even with bash's igncr option mapfile -t IDENTITY_FIELDS < <(az identity show \ --name "$MANAGED_IDENTITY_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "[clientId, principalId, id]" \ --output tsv \ - --only-show-errors) + --only-show-errors | tr -d '\r') IDENTITY_CLIENT_ID="${IDENTITY_FIELDS[0]:-}" IDENTITY_PRINCIPAL_ID="${IDENTITY_FIELDS[1]:-}" IDENTITY_ID="${IDENTITY_FIELDS[2]:-}" @@ -574,12 +575,13 @@ fi # the service and used verbatim by the web app: https://.azconfig.io on Azure, # https://.azure.localhost.localstack.cloud:4566 on the emulator. echo "Retrieving the endpoint and the resource id of the [$APP_CONFIG_NAME] App Configuration store..." +# tr drops the carriage return the Azure CLI ends each line with on Windows: mapfile keeps it, even with bash's igncr option mapfile -t APP_CONFIG_FIELDS < <(az appconfig show \ --name "$APP_CONFIG_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "[endpoint, id]" \ --output tsv \ - --only-show-errors) + --only-show-errors | tr -d '\r') APP_CONFIG_ENDPOINT="${APP_CONFIG_FIELDS[0]:-}" APP_CONFIG_ID="${APP_CONFIG_FIELDS[1]:-}" @@ -661,12 +663,13 @@ fi # https://.vault.azure.net/ on Azure, https://.vault.azure.localhost.localstack.cloud:4566/ on # the emulator. echo "Retrieving the vault URI and the resource id of the [$KEY_VAULT_NAME] key vault..." +# tr drops the carriage return the Azure CLI ends each line with on Windows: mapfile keeps it, even with bash's igncr option mapfile -t KEY_VAULT_FIELDS < <(az keyvault show \ --name "$KEY_VAULT_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "[properties.vaultUri, id]" \ --output tsv \ - --only-show-errors) + --only-show-errors | tr -d '\r') KEY_VAULT_URI="${KEY_VAULT_FIELDS[0]:-}" KEY_VAULT_ID="${KEY_VAULT_FIELDS[1]:-}" diff --git a/samples/web-app-app-configuration/python/scripts/deploy.sh b/samples/web-app-app-configuration/python/scripts/deploy.sh index 929363a..99dec68 100644 --- a/samples/web-app-app-configuration/python/scripts/deploy.sh +++ b/samples/web-app-app-configuration/python/scripts/deploy.sh @@ -459,12 +459,13 @@ fi # Retrieve the clientId, principalId and resource id of the user-assigned managed identity echo "Retrieving the clientId, principalId and resource id of the [$MANAGED_IDENTITY_NAME] managed identity..." +# tr drops the carriage return the Azure CLI ends each line with on Windows: mapfile keeps it, even with bash's igncr option mapfile -t IDENTITY_FIELDS < <(az identity show \ --name "$MANAGED_IDENTITY_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "[clientId, principalId, id]" \ --output tsv \ - --only-show-errors) + --only-show-errors | tr -d '\r') IDENTITY_CLIENT_ID="${IDENTITY_FIELDS[0]:-}" IDENTITY_PRINCIPAL_ID="${IDENTITY_FIELDS[1]:-}" IDENTITY_ID="${IDENTITY_FIELDS[2]:-}" @@ -574,12 +575,13 @@ fi # the service and used verbatim by the web app: https://.azconfig.io on Azure, # https://.azure.localhost.localstack.cloud:4566 on the emulator. echo "Retrieving the endpoint and the resource id of the [$APP_CONFIG_NAME] App Configuration store..." +# tr drops the carriage return the Azure CLI ends each line with on Windows: mapfile keeps it, even with bash's igncr option mapfile -t APP_CONFIG_FIELDS < <(az appconfig show \ --name "$APP_CONFIG_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "[endpoint, id]" \ --output tsv \ - --only-show-errors) + --only-show-errors | tr -d '\r') APP_CONFIG_ENDPOINT="${APP_CONFIG_FIELDS[0]:-}" APP_CONFIG_ID="${APP_CONFIG_FIELDS[1]:-}" @@ -661,12 +663,13 @@ fi # https://.vault.azure.net/ on Azure, https://.vault.azure.localhost.localstack.cloud:4566/ on # the emulator. echo "Retrieving the vault URI and the resource id of the [$KEY_VAULT_NAME] key vault..." +# tr drops the carriage return the Azure CLI ends each line with on Windows: mapfile keeps it, even with bash's igncr option mapfile -t KEY_VAULT_FIELDS < <(az keyvault show \ --name "$KEY_VAULT_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "[properties.vaultUri, id]" \ --output tsv \ - --only-show-errors) + --only-show-errors | tr -d '\r') KEY_VAULT_URI="${KEY_VAULT_FIELDS[0]:-}" KEY_VAULT_ID="${KEY_VAULT_FIELDS[1]:-}" diff --git a/samples/web-app-cosmosdb-nosql-api/python/scripts/... b/samples/web-app-cosmosdb-nosql-api/python/scripts/... deleted file mode 100644 index e69de29..0000000 diff --git a/samples/web-app-mysql-flexible-server/dotnet/bicep/deploy.sh b/samples/web-app-mysql-flexible-server/dotnet/bicep/deploy.sh index 4d27607..48ba9a1 100755 --- a/samples/web-app-mysql-flexible-server/dotnet/bicep/deploy.sh +++ b/samples/web-app-mysql-flexible-server/dotnet/bicep/deploy.sh @@ -1,5 +1,16 @@ #!/bin/bash +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables PREFIX="${PREFIX:-local}" SUFFIX="${SUFFIX:-test}" @@ -310,7 +321,7 @@ echo "You can visit your app at: $WEB_APP_URL" echo "Pinging [$WEB_APP_URL] to verify the web app responds..." HTTP_CODE="000" for attempt in $(seq 1 12); do - HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") + HTTP_CODE=$(curl -s -o "$(null_device)" -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") if [ "$HTTP_CODE" = "200" ]; then echo "Web app responded with HTTP 200. Deployment verified successfully." break diff --git a/samples/web-app-mysql-flexible-server/dotnet/scripts/deploy.sh b/samples/web-app-mysql-flexible-server/dotnet/scripts/deploy.sh index ade1ba2..6ec74f4 100755 --- a/samples/web-app-mysql-flexible-server/dotnet/scripts/deploy.sh +++ b/samples/web-app-mysql-flexible-server/dotnet/scripts/deploy.sh @@ -1,5 +1,16 @@ #!/bin/bash +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables PREFIX="${PREFIX:-local}" SUFFIX="${SUFFIX:-test}" @@ -1132,7 +1143,7 @@ echo "You can visit your app at: $WEB_APP_URL" echo "Pinging [$WEB_APP_URL] to verify the web app responds..." HTTP_CODE="000" for attempt in $(seq 1 12); do - HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") + HTTP_CODE=$(curl -s -o "$(null_device)" -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") if [ "$HTTP_CODE" = "200" ]; then echo "Web app responded with HTTP 200. Deployment verified successfully." break diff --git a/samples/web-app-mysql-flexible-server/dotnet/terraform/deploy.sh b/samples/web-app-mysql-flexible-server/dotnet/terraform/deploy.sh index d07794d..5ef16f1 100755 --- a/samples/web-app-mysql-flexible-server/dotnet/terraform/deploy.sh +++ b/samples/web-app-mysql-flexible-server/dotnet/terraform/deploy.sh @@ -1,5 +1,16 @@ #!/bin/bash +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables PREFIX="${PREFIX:-local}" SUFFIX="${SUFFIX:-test}" @@ -263,7 +274,7 @@ echo "You can visit your app at: $WEB_APP_URL" echo "Pinging [$WEB_APP_URL] to verify the web app responds..." HTTP_CODE="000" for attempt in $(seq 1 12); do - HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") + HTTP_CODE=$(curl -s -o "$(null_device)" -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") if [ "$HTTP_CODE" = "200" ]; then echo "Web app responded with HTTP 200. Deployment verified successfully." break diff --git a/samples/web-app-mysql-flexible-server/python/bicep/deploy.sh b/samples/web-app-mysql-flexible-server/python/bicep/deploy.sh index 9d9c056..556c63b 100755 --- a/samples/web-app-mysql-flexible-server/python/bicep/deploy.sh +++ b/samples/web-app-mysql-flexible-server/python/bicep/deploy.sh @@ -1,5 +1,16 @@ #!/bin/bash +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables PREFIX="${PREFIX:-local}" SUFFIX="${SUFFIX:-test}" @@ -310,7 +321,7 @@ echo "You can visit your app at: $WEB_APP_URL" echo "Pinging [$WEB_APP_URL] to verify the web app responds..." HTTP_CODE="000" for attempt in $(seq 1 12); do - HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") + HTTP_CODE=$(curl -s -o "$(null_device)" -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") if [ "$HTTP_CODE" = "200" ]; then echo "Web app responded with HTTP 200. Deployment verified successfully." break diff --git a/samples/web-app-mysql-flexible-server/python/scripts/deploy.sh b/samples/web-app-mysql-flexible-server/python/scripts/deploy.sh index 9787b81..f4c35e7 100755 --- a/samples/web-app-mysql-flexible-server/python/scripts/deploy.sh +++ b/samples/web-app-mysql-flexible-server/python/scripts/deploy.sh @@ -1,5 +1,16 @@ #!/bin/bash +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables PREFIX="${PREFIX:-local}" SUFFIX="${SUFFIX:-test}" @@ -1132,7 +1143,7 @@ echo "You can visit your app at: $WEB_APP_URL" echo "Pinging [$WEB_APP_URL] to verify the web app responds..." HTTP_CODE="000" for attempt in $(seq 1 12); do - HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") + HTTP_CODE=$(curl -s -o "$(null_device)" -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") if [ "$HTTP_CODE" = "200" ]; then echo "Web app responded with HTTP 200. Deployment verified successfully." break diff --git a/samples/web-app-mysql-flexible-server/python/terraform/deploy.sh b/samples/web-app-mysql-flexible-server/python/terraform/deploy.sh index d41747e..d7d02a1 100755 --- a/samples/web-app-mysql-flexible-server/python/terraform/deploy.sh +++ b/samples/web-app-mysql-flexible-server/python/terraform/deploy.sh @@ -1,5 +1,16 @@ #!/bin/bash +# Where curl discards a response body. Git Bash and MSYS2 shells hand a Windows curl /dev/null +# unchanged when MSYS_NO_PATHCONV=1 is set, as the README asks, and it cannot open that path, so it +# gets the NUL device instead; MSYS2's own curl, under /usr/bin, opens /dev/null as on Linux and macOS. +null_device() { + if command -v cygpath >/dev/null 2>&1 && [[ "$(command -v curl)" != /usr/bin/* ]]; then + echo NUL + else + echo /dev/null + fi +} + # Variables PREFIX="${PREFIX:-local}" SUFFIX="${SUFFIX:-test}" @@ -263,7 +274,7 @@ echo "You can visit your app at: $WEB_APP_URL" echo "Pinging [$WEB_APP_URL] to verify the web app responds..." HTTP_CODE="000" for attempt in $(seq 1 12); do - HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") + HTTP_CODE=$(curl -s -o "$(null_device)" -w '%{http_code}' --max-time 10 "$WEB_APP_URL" || echo "000") if [ "$HTTP_CODE" = "200" ]; then echo "Web app responded with HTTP 200. Deployment verified successfully." break diff --git a/samples/web-app-sql-database/dotnet/scripts/call-web-app.sh b/samples/web-app-sql-database/dotnet/scripts/call-web-app.sh index b48418c..5daec1d 100755 --- a/samples/web-app-sql-database/dotnet/scripts/call-web-app.sh +++ b/samples/web-app-sql-database/dotnet/scripts/call-web-app.sh @@ -206,12 +206,14 @@ call_web_app() { echo "Failed to retrieve LocalStack proxy port" fi + container_ip_reachable=false if [ -n "$container_ip" ] && [ -n "$container_port" ]; then # Call the web app via the container IP address echo "Calling web app [$web_app_name] via container IP address [$container_ip]..." curl --max-time 10 -s "http://$container_ip:$container_port/" 1> /dev/null if [ $? == 0 ]; then + container_ip_reachable=true echo "Web app call via container IP address [$container_ip] succeeded." else echo "Web app call via container IP address [$container_ip] failed." @@ -249,6 +251,13 @@ call_web_app() { fi echo "Validating certificate from Key Vault..." + if [[ $container_ip_reachable != true ]]; then + # The check reaches the app's HTTPS port through the container IP address, which the host can + # reach only when Docker runs natively on Linux: Docker Desktop on Windows and macOS does not + # route it. Say so and skip, rather than failing a deployment that works. + echo "The container IP address [$container_ip] is not reachable from this host; skipping the Key Vault certificate check." + return 0 + fi if ! KV_RESPONSE=$(curl --max-time 10 -fsSk "https://$container_ip:8443/api/certificate"); then echo "Failed to call https://$container_ip:8443/api/certificate (is HTTPS on port 8443 enabled?)" exit 1 diff --git a/samples/web-app-sql-database/python/scripts/call-web-app.sh b/samples/web-app-sql-database/python/scripts/call-web-app.sh index 74bf6ff..9d7434f 100755 --- a/samples/web-app-sql-database/python/scripts/call-web-app.sh +++ b/samples/web-app-sql-database/python/scripts/call-web-app.sh @@ -206,12 +206,14 @@ call_web_app() { echo "Failed to retrieve LocalStack proxy port" fi + container_ip_reachable=false if [ -n "$container_ip" ] && [ -n "$container_port" ]; then # Call the web app via the container IP address echo "Calling web app [$web_app_name] via container IP address [$container_ip]..." curl --max-time 10 -s "http://$container_ip:$container_port/" 1> /dev/null if [ $? == 0 ]; then + container_ip_reachable=true echo "Web app call via container IP address [$container_ip] succeeded." else echo "Web app call via container IP address [$container_ip] failed." @@ -249,6 +251,13 @@ call_web_app() { fi echo "Validating certificate from Key Vault..." + if [[ $container_ip_reachable != true ]]; then + # The check reaches the app's HTTPS port through the container IP address, which the host can + # reach only when Docker runs natively on Linux: Docker Desktop on Windows and macOS does not + # route it. Say so and skip, rather than failing a deployment that works. + echo "The container IP address [$container_ip] is not reachable from this host; skipping the Key Vault certificate check." + return 0 + fi if ! KV_RESPONSE=$(curl --max-time 10 -fsSk "https://$container_ip:8443/api/certificate"); then # Only reachable when the app serves HTTPS itself (python app.py); under gunicorn the port # is closed. Say so and skip, rather than comparing two empty thumbprints as equal.