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
102 changes: 102 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Test init hooks

on:
push:
paths-ignore:
- '**.md'
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

jobs:
lstk:
name: Init hooks with lstk
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: example-localstack-cli
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install lstk
run: |
npm install -g @localstack/lstk
lstk setup aws --force --non-interactive

- name: Start LocalStack with the mounted init hook
env:
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
run: lstk start

- name: Run tests
run: ./test.sh

- name: Show LocalStack logs
if: failure()
run: lstk logs | tail -200

docker-compose:
name: Init hooks with docker compose
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: example-docker-compose
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install lstk
run: |
npm install -g @localstack/lstk
lstk setup aws --force --non-interactive

- name: Start LocalStack
env:
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
run: |
docker compose up -d
for i in $(seq 1 60); do
curl -sf localhost:4566/_localstack/health > /dev/null && exit 0
sleep 2
done
docker compose logs
exit 1

- name: Run tests
run: ./test.sh

- name: Stop LocalStack and check the shutdown hook exported the state
run: |
docker compose down --timeout 60
ls -l volume/my-pod.zip
unzip -l volume/my-pod.zip | grep secretsmanager

- name: Load the exported state into a fresh LocalStack
env:
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
run: |
lstk start
lstk load ./volume/my-pod.zip
lstk aws secretsmanager get-secret-value --secret-id my-platform-secret --query SecretString --output text | grep hello-there

- name: Show LocalStack logs
if: failure()
run: |
docker compose logs --tail 200 || true
lstk logs | tail -200 || true
10 changes: 7 additions & 3 deletions example-docker-compose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This example has two scripts that are executed as soon as localstack is ready:
* a python script that creates parameters based on a list of values defined in the script

And one script that runs when localstack shuts down:
* a shell script that saves the state into `./volume/my-pod.zip` before localstack shuts down
* a shell script that exports the state into `./volume/my-pod.zip` before localstack shuts down

## Start LocalStack

Expand Down Expand Up @@ -98,5 +98,9 @@ lstk aws ssm get-parameter --name "/my/param-1"

## Shutdown

After you've shut down localstack, you should see a zipfile `my-pod.zip` in `./volume`, which is created by the shutdown hook.
This is a state export that you can import into a fresh localstack instance with `localstack state import <...>`.
After you've shut down localstack, you should see a zipfile `my-pod.zip` in `./volume`, which is created by the shutdown hook through the `/_localstack/pods/state` endpoint.
This is a state export that you can load into a fresh localstack instance with `lstk load ./volume/my-pod.zip`.

## Test

With LocalStack running, `./test.sh` checks that both ready hooks succeeded and created the secret and parameters.
6 changes: 6 additions & 0 deletions example-docker-compose/on-ready-hooks/01-init-secrets.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#!/bin/bash
set -eo pipefail

# Init hooks do not inherit AWS settings, so default to the LocalStack test credentials
export AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-us-east-1}
export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-test}
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-test}

aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name my-platform-secret
aws --endpoint-url=http://localhost:4566 secretsmanager put-secret-value --secret-id my-platform-secret --secret-string "{\"MY_TOKEN\": \"${MY_SECRET_TOKEN_VALUE}\"}"
Expand Down
2 changes: 2 additions & 0 deletions example-docker-compose/on-ready-hooks/02-init-params.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

os.environ["AWS_ENDPOINT_URL"] = "http://localhost:4566"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
os.environ.setdefault("AWS_ACCESS_KEY_ID", "test")
os.environ.setdefault("AWS_SECRET_ACCESS_KEY", "test")

ssm = boto3.client("ssm")

Expand Down
Binary file not shown.
6 changes: 3 additions & 3 deletions example-docker-compose/on-shutdown-hooks/save-pod.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash

# use the localstack CLI within the container
# and save the state it into the localstack volume which is mounted into `./volume`.
/opt/code/localstack/.venv/bin/localstack state export /var/lib/localstack/my-pod.zip
# export the state through the LocalStack API
# and save it into the localstack volume which is mounted into `./volume`.
curl -sf -o /var/lib/localstack/my-pod.zip http://localhost:4566/_localstack/pods/state
44 changes: 44 additions & 0 deletions example-docker-compose/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash
# Check the ready hooks ran and created their resources:
# - 01-init-secrets.sh creates my-platform-secret from MY_SECRET_TOKEN_VALUE
# - 02-init-params.py creates the /my/param-1 and /my/param-2 parameters
set -euo pipefail

ENDPOINT=${ENDPOINT:-http://localhost:4566}
MAX_ATTEMPTS=${MAX_ATTEMPTS:-60}
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=us-east-1

ready=""
for attempt in $(seq 1 "$MAX_ATTEMPTS"); do
ready=$(curl -s "$ENDPOINT/_localstack/init/ready" || true)
echo "$ready" | jq -e '.completed == true' > /dev/null 2>&1 && break
echo "Attempt $attempt/$MAX_ATTEMPTS: ready hooks not finished"
sleep 2
done
echo "$ready" | jq .
for script in 01-init-secrets.sh 02-init-params.py; do
if ! echo "$ready" | jq -e --arg s "$script" 'any(.scripts[]; .name == $s and .state == "SUCCESSFUL")' > /dev/null; then
echo "Test failed: ready hook $script did not succeed"
exit 1
fi
done
echo "PASS: both ready hooks succeeded"

secret=$(aws --endpoint-url "$ENDPOINT" secretsmanager get-secret-value --secret-id my-platform-secret \
--query SecretString --output text)
if [[ "$secret" != *'hello-there'* ]]; then
echo "Test failed: my-platform-secret is '$secret'"
exit 1
fi
echo "PASS: secret created with the value from MY_SECRET_TOKEN_VALUE"

for param in "/my/param-1=my-param" "/my/param-2=my-other-param"; do
value=$(aws --endpoint-url "$ENDPOINT" ssm get-parameter --name "${param%%=*}" --query Parameter.Value --output text)
if [ "$value" != "${param#*=}" ]; then
echo "Test failed: ${param%%=*} is '$value'"
exit 1
fi
done
echo "PASS: SSM parameters created"

echo "Test passed: the ready hooks seeded LocalStack"
4 changes: 4 additions & 0 deletions example-localstack-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ You can check that the stage was executed successfully:
]
}
```

## Test

With LocalStack running, `./test.sh` checks that `init-vpc.sh` succeeded and created the VPC with three isolated subnets.
6 changes: 6 additions & 0 deletions example-localstack-cli/init-vpc.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#!/bin/bash
set -eo pipefail

# Init hooks do not inherit AWS settings, so default to the LocalStack test credentials
export AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-us-east-1}
export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-test}
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-test}

# variables that can be overwritten via environment variables
vpc_name=${VPC_NAME-"MyVPC"}
Expand Down
40 changes: 40 additions & 0 deletions example-localstack-cli/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/bash
# Check the init-vpc.sh ready hook mounted through .lstk/config.toml ran and
# created the VPC with its three tagged subnets.
set -euo pipefail

ENDPOINT=${ENDPOINT:-http://localhost:4566}
MAX_ATTEMPTS=${MAX_ATTEMPTS:-60}
VPC_NAME=${VPC_NAME:-MyVPC}

ready=""
for attempt in $(seq 1 "$MAX_ATTEMPTS"); do
ready=$(curl -s "$ENDPOINT/_localstack/init/ready" || true)
echo "$ready" | jq -e '.completed == true' > /dev/null 2>&1 && break
echo "Attempt $attempt/$MAX_ATTEMPTS: ready hooks not finished"
sleep 2
done
echo "$ready" | jq .
if ! echo "$ready" | jq -e 'any(.scripts[]; .name == "init-vpc.sh" and .state == "SUCCESSFUL")' > /dev/null; then
echo "Test failed: ready hook init-vpc.sh did not succeed"
exit 1
fi
echo "PASS: init-vpc.sh succeeded"

vpc_id=$(lstk aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$VPC_NAME" --query 'Vpcs[0].VpcId' --output text)
if [ -z "$vpc_id" ] || [ "$vpc_id" = "None" ]; then
echo "Test failed: no VPC tagged Name=$VPC_NAME"
exit 1
fi
echo "PASS: found VPC $vpc_id"

subnets=$(lstk aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$vpc_id" "Name=tag:aws-cdk:subnet-type,Values=Isolated" \
--query 'length(Subnets)' --output text)
if [ "$subnets" != "3" ]; then
echo "Test failed: expected 3 isolated subnets in $vpc_id, found $subnets"
exit 1
fi
echo "PASS: VPC has 3 isolated subnets"

echo "Test passed: the init hook created the VPC"
Loading