Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions .github/workflows/samples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
name: Test samples on LocalStack for Azure

# Creates the AKS cluster on the LocalStack for Azure emulator, deploys a sample's Python or .NET web
# app with its numbered scripts, one job per app, and checks the app end to end with
# tests/test-vacation-planner.sh. As in the Samples CI of localstack-azure-samples, a pull request or a
# push runs the apps it changes (every app when a file all of them use changes), and a manual run can
# choose to run every app.

on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
branches:
- main
Comment thread
paolosalvatori marked this conversation as resolved.
workflow_dispatch:
inputs:
run_mode:
description: "'all' runs every sample, 'changed' runs only the samples with modified files"
required: false
type: choice
options:
- all
- changed
default: changed

permissions: {}

# A new push to the same pull request cancels the run it supersedes: with one job per sample and
# language, a stale run would otherwise hold up to twenty runners for the length of a deployment
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
setup:
name: Select samples
# Pull requests from forks get no secrets, so lstk could not start the emulator there
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
matrix: ${{ steps.select.outputs.matrix }}
has_samples: ${{ steps.select.outputs.has_samples }}

steps:
- name: Check out repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# The full history, so that 'changed' mode can compare with the base commit
fetch-depth: 0

- name: Select the samples to run
id: select
env:
# Pull requests and pushes have no inputs: they run the samples they change
RUN_MODE: ${{ inputs.run_mode || 'changed' }}
EVENT_NAME: ${{ github.event_name }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
run: |
# Every app a job can deploy, as samples/<sample>/<language>, so a new sample needs no change here
apps=$(printf '%s\n' samples/*/*/scripts/05-deploy-app.sh | sed 's|/scripts/05-deploy-app\.sh$||')
selected=$apps

if [ "$RUN_MODE" = changed ]; then
case $EVENT_NAME in
pull_request) base=$PR_BASE_SHA ;;
push) base=$PUSH_BEFORE_SHA ;;
*) base=origin/main ;;
esac
if git rev-parse --quiet --verify "$base^{commit}" > /dev/null; then
changed=$(git diff --name-only "$base...HEAD")
echo "Files changed since $base:"
echo "$changed"
# Every job creates the cluster with the same script and checks the app with the same test, so a
# change to either, or to this workflow, runs every app; otherwise an app runs when its folder changes
shared='^(scripts/01-system-assigned-managed-identity\.sh|tests/test-vacation-planner\.sh|\.github/workflows/samples\.yml)$'
if ! grep -q -E "$shared" <<< "$changed"; then
selected=$(while read -r app; do
if grep -q "^$app/" <<< "$changed"; then echo "$app"; fi
done <<< "$apps")
fi
else
# A push that creates the branch or rewrites its history leaves no base commit to compare with
echo "Base commit '$base' not found: running every sample"
fi
fi

matrix=$(jq -n -R -c '[inputs | select(length > 0) | split("/") | {sample: .[1], language: .[2]}] | {include: .}' <<< "$selected")
echo "Samples to run: $(jq -r '[.include[] | "\(.sample) (\(.language))"] | if length > 0 then join(", ") else "none" end' <<< "$matrix")"
{
echo "matrix=$matrix"
echo "has_samples=$(jq '.include | length > 0' <<< "$matrix")"
} >> "$GITHUB_OUTPUT"

sample:
name: ${{ matrix.sample }} (${{ matrix.language }})
needs: setup
# Skip the job when the run changes no sample
if: needs.setup.outputs.has_samples == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
env:
SAMPLE: ${{ matrix.sample }}
LANGUAGE: ${{ matrix.language }}

steps:
- name: Check out repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Install lstk
# Pinned like actions/checkout, so an lstk release cannot change every run at once: bump it deliberately
run: npm install -g @localstack/lstk@1.4.0

- name: Install sqlcmd
# The Azure SQL Database sample seeds its database with sqlcmd, which the runner image does not ship
if: matrix.sample == 'web-app-sql-database'
run: |
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc > /dev/null
curl -fsSL "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list" | sudo tee /etc/apt/sources.list.d/mssql-release.list > /dev/null
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18
echo "/opt/mssql-tools18/bin" >> "$GITHUB_PATH"

- name: Create the SSH key the cluster script provisions the node pools with
run: ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa -q

- name: Start the LocalStack for Azure emulator
env:
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
# lstk forwards LOCALSTACK_-prefixed variables to the emulator. The Azure SQL Database sample
# needs the SQL Server EULA accepted, as its README and the localstack-azure-samples CI do
LOCALSTACK_MSSQL_ACCEPT_EULA: "Y"
# The emulator looks up the k3d release behind each AKS cluster on api.github.com. Anonymous
# calls share the runner IP's 60 requests an hour, which twenty parallel jobs can exhaust, so pass
# the run's own read-only token, as the localstack-azure-samples CI does
LOCALSTACK_GITHUB_API_TOKEN: ${{ github.token }}
run: |
lstk start --type azure --non-interactive
lstk az start-interception

# The script prompts for the network configuration and both node pool sizes:
# pick option 1 (Azure network plugin and policy) and keep the default sizes
- name: Create the AKS cluster
working-directory: scripts
run: printf '1\n\n\n' | ./01-system-assigned-managed-identity.sh

- name: Deploy the sample
working-directory: samples/${{ matrix.sample }}/${{ matrix.language }}/scripts
env:
# Answer the Azure Files sample's two provisioning menus; the other samples do not read these
PROVISIONING_MODE: static
FILE_SHARE_PROTOCOL: smb
run: |
./01-deploy-resources.sh
./02-build-docker-image.sh
./04-push-docker-image.sh
./05-deploy-app.sh

- name: Test the web app
run: |
variables="samples/$SAMPLE/$LANGUAGE/scripts/00-variables.sh"
namespace=$(sed -n "s/^NAMESPACE=\"\(.*\)\"/\1/p" "$variables")
service=$(sed -n "s/^SERVICE_NAME=\"\(.*\)\"/\1/p" "$variables")
./tests/test-vacation-planner.sh "$namespace" "$service"

- name: Show cluster state and emulator logs
if: failure()
run: |
kubectl get pods --all-namespaces -o wide || true
kubectl get events --all-namespaces --sort-by=.lastTimestamp | tail -50 || true
for ns in $(kubectl get namespaces -o name | sed 's|namespace/||' | grep vacation-planner); do
kubectl describe pods --namespace "$ns" | tail -80 || true
kubectl logs --namespace "$ns" --all-containers --tail=100 -l "app=$ns" || true
done
lstk logs | tail -300 || true
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ Start the emulator and point the Azure CLI at it:
export LOCALSTACK_AUTH_TOKEN=<your_auth_token>

# Start the LocalStack Azure emulator
IMAGE_NAME=localstack/localstack-azure localstack start -d
localstack wait -t 60
lstk start --type azure

# Route all Azure CLI calls to the emulator
lstk az start-interception
Expand Down Expand Up @@ -184,6 +183,16 @@ Both versions of the app also expose `GET /health`, the endpoint their Kubernete

Alternatively, you can use a terminal UI such as [k9s](https://k9scli.io/) to select the service and start a port-forward interactively (press `<shift-f>` on a selected service or pod).

### Testing a deployed sample

[tests/test-vacation-planner.sh](tests/test-vacation-planner.sh) checks a deployed web app end to end through its Kubernetes service: it waits for the Deployment to roll out, checks that `GET /health` reports the data service as reachable, then adds an activity through the web form, checks that the page lists it, deletes it, and checks that it is gone while a second activity stays listed. It drives both the Python (Flask) and the .NET (Razor Pages) apps. Pass the sample's namespace and service name, for example:

```bash
./tests/test-vacation-planner.sh vacation-planner-blob vacation-planner-blob
```

The [Test samples on LocalStack for Azure](.github/workflows/samples.yml) workflow runs this against the emulator, one job per sample app, in Python and .NET. Each job creates the cluster with `scripts/01-system-assigned-managed-identity.sh`, deploys the app with its numbered scripts, and runs the test. A pull request or a push to `main` runs the apps whose folders it changes, and all 20 when it changes the cluster script, the test or the workflow. A manual run tests the changed apps by default, or all 20 with `run_mode: all`.

## Tutorials

Beyond the Vacation Planner samples, the repository includes standalone tutorials that exercise specific AKS capabilities. Unlike the samples above, they do not deploy the web app.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Variables

# Azure Resources
PREFIX='zeus'
PREFIX='local'
SUFFIX='test'
LOCATION='italynorth'
RESOURCE_GROUP_NAME="${PREFIX}-rg"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Variables

# Azure Resources
PREFIX='zeus'
PREFIX='local'
SUFFIX='test'
LOCATION='italynorth'
RESOURCE_GROUP_NAME="${PREFIX}-rg"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ fi

echo "Creating SQL login [$DATABASE_USER_NAME] on server [$SQL_SERVER_FQDN]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d master \
Expand All @@ -157,6 +158,7 @@ fi

echo "Creating database user [$DATABASE_USER_NAME] in database [$SQL_DATABASE_NAME]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d "$SQL_DATABASE_NAME" \
Expand All @@ -171,6 +173,7 @@ fi

echo "Granting roles db_datareader, db_datawriter, db_ddladmin to [$DATABASE_USER_NAME]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d "$SQL_DATABASE_NAME" \
Expand All @@ -185,6 +188,7 @@ fi

echo "Creating table dbo.Activities in database [$SQL_DATABASE_NAME]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d "$SQL_DATABASE_NAME" \
Expand All @@ -200,6 +204,7 @@ fi
# Insert data
echo "Inserting test data into [Activities] table..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-d "$SQL_DATABASE_NAME" \
-U "$DATABASE_USER_NAME" \
-P "$DATABASE_USER_PASSWORD" \
Expand Down Expand Up @@ -228,6 +233,7 @@ fi
# Query data
echo "Querying test data from [Activities] table..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-d "$SQL_DATABASE_NAME" \
-U "$DATABASE_USER_NAME" \
-P "$DATABASE_USER_PASSWORD" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ fi

echo "Creating SQL login [$DATABASE_USER_NAME] on server [$SQL_SERVER_FQDN]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d master \
Expand All @@ -157,6 +158,7 @@ fi

echo "Creating database user [$DATABASE_USER_NAME] in database [$SQL_DATABASE_NAME]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d "$SQL_DATABASE_NAME" \
Expand All @@ -171,6 +173,7 @@ fi

echo "Granting roles db_datareader, db_datawriter, db_ddladmin to [$DATABASE_USER_NAME]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d "$SQL_DATABASE_NAME" \
Expand All @@ -185,6 +188,7 @@ fi

echo "Creating table dbo.Activities in database [$SQL_DATABASE_NAME]..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-U "$ADMIN_USER" \
-P "$ADMIN_PASSWORD" \
-d "$SQL_DATABASE_NAME" \
Expand All @@ -200,6 +204,7 @@ fi
# Insert data
echo "Inserting test data into [Activities] table..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-d "$SQL_DATABASE_NAME" \
-U "$DATABASE_USER_NAME" \
-P "$DATABASE_USER_PASSWORD" \
Expand Down Expand Up @@ -228,6 +233,7 @@ fi
# Query data
echo "Querying test data from [Activities] table..."
sqlcmd -S "$SQL_SERVER_FQDN" \
-N -C \
-d "$SQL_DATABASE_NAME" \
-U "$DATABASE_USER_NAME" \
-P "$DATABASE_USER_PASSWORD" \
Expand Down
6 changes: 3 additions & 3 deletions scripts/01-user-assigned-managed-identity.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ registering_extensions=()
ok=0

# Print the menu
echo "===================================="
echo "Run Docker Container (1-3): "
echo "===================================="
echo "======================================"
echo "Select Network Plugin and Policy (1-3): "
echo "======================================"
options=(
"Azure Network Plugin + Azure Network Policy"
"Azure Network Plugin + Cilium Network Policy"
Expand Down
Loading
Loading