From 3f9ac326dd93f57906201aa6196dad0466533079 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Tue, 28 Jul 2026 11:26:38 +1200 Subject: [PATCH 1/3] Stop tracking generated buckets.properties Co-Authored-By: Claude Sonnet 5 --- src/main/resources/buckets.properties | 2 -- 1 file changed, 2 deletions(-) delete mode 100755 src/main/resources/buckets.properties diff --git a/src/main/resources/buckets.properties b/src/main/resources/buckets.properties deleted file mode 100755 index 810384b..0000000 --- a/src/main/resources/buckets.properties +++ /dev/null @@ -1,2 +0,0 @@ -shipment-picture-bucket=shipment-picture-bucket-accepted-firefly -shipment-picture-bucket-validator=shipment-picture-lambda-validator-bucket-accepted-firefly From 80e6c9fabccaae7907435c6f89c5e912725a3dcf Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Tue, 28 Jul 2026 12:11:54 +1200 Subject: [PATCH 2/3] Migrate from the localstack CLI to lstk Replace localstack CLI usage across the Makefile, README, and CI workflow with the lstk equivalents (lstk start/stop/logs/tf), install lstk in CI via npm instead of the setup-localstack action, and add a checked-in .lstk/config.toml for the image/tag/log-level settings that have no env-var equivalent. Also fixes a few things surfaced while getting `make test`/CI green again: bump Lombok and Testcontainers so the build works on current JDK/Docker versions, swap the in-container awslocal calls for `aws --endpoint-url` (lstk itself can't run inside the LocalStack container), and fail fast with a clear message when LOCALSTACK_AUTH_TOKEN is missing instead of silently waiting out Testcontainers' ~60s readiness timeout. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 30 +++++++------------ .gitignore | 2 ++ .lstk/config.toml | 12 ++++++++ Makefile | 18 ++++------- README.md | 15 +++++----- pom.xml | 13 ++++++-- shipment-picture-lambda-validator/pom.xml | 4 +-- .../main/resources/lambda_update_script.sh | 6 ++-- .../LocalStackSetupConfigurations.java | 30 ++++++++++++++++--- .../ShipmentServiceIntegrationTest.java | 2 +- 10 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 .lstk/config.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04cc9b6..7504eac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,11 +27,6 @@ jobs: with: node-version: '22' - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - name: Set up JDK uses: actions/setup-java@v2 with: @@ -41,23 +36,18 @@ jobs: - name: Set up Maven run: sudo apt-get install -y maven + - name: Install lstk + run: npm install -g @localstack/lstk + - name: Start LocalStack - uses: LocalStack/setup-localstack@v0.2.4 - with: - image-tag: 'latest' - use-pro: 'true' - configuration: LS_LOG=trace - install-awslocal: 'true' env: LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }} + run: | + lstk start - name: Install Terraform uses: hashicorp/setup-terraform@v3 - - name: Install tflocal - run: | - pip install --upgrade terraform-local - - name: Build project run: | cd shipment-picture-lambda-validator @@ -66,9 +56,9 @@ jobs: - name: Deploy using Terraform run: | cd terraform - tflocal init - tflocal plan - tflocal apply --auto-approve + lstk tf init + lstk tf plan + lstk tf apply --auto-approve - name: Build frontend run: | @@ -79,11 +69,11 @@ jobs: - name: Show LocalStack logs if: always() run: | - localstack logs + lstk logs - name: Stop LocalStack run: | - localstack stop + lstk stop - name: Run Testcontainers tests env: diff --git a/.gitignore b/.gitignore index 38e54db..5bb1014 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,8 @@ src/main/shipment-list-frontend/npm-debug.log* src/main/shipment-list-frontend/yarn-debug.log* src/main/shipment-list-frontend/yarn-error.log* +src/main/resources/buckets.properties + # terraform terraform/.terraform/ terraform/terraform.tfstate diff --git a/.lstk/config.toml b/.lstk/config.toml new file mode 100644 index 0000000..026d3bb --- /dev/null +++ b/.lstk/config.toml @@ -0,0 +1,12 @@ +# lstk auto-discovers ./.lstk/config.toml (relative to cwd) before falling back to the +# per-user config, so `lstk start` in .github/workflows/ci.yml picks this up with no extra flag. +# Run 'lstk config path' to see where your personal (non-CI) config lives instead. + +[[containers]] +type = "aws" +image = "localstack/localstack-pro" +tag = "latest" +env = ["ci"] + +[env.ci] +LS_LOG = "trace" diff --git a/Makefile b/Makefile index 444d07b..02eeaa9 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,8 @@ usage: check: @command -v docker > /dev/null 2>&1 || { echo "Docker is not installed. Please install Docker and try again."; exit 1; } @command -v node > /dev/null 2>&1 || { echo "Node.js is not installed. Please install Node.js and try again."; exit 1; } - @command -v localstack > /dev/null 2>&1 || { echo "LocalStack is not installed. Please install LocalStack and try again."; exit 1; } @command -v terraform > /dev/null 2>&1 || { echo "Terraform is not installed. Please install Terraform and try again."; exit 1; } - @command -v tflocal > /dev/null 2>&1 || { echo "tflocal is not installed. Please install tflocal and try again."; exit 1; } + @command -v lstk > /dev/null 2>&1 || { echo "lstk is not installed. Please install lstk and try again."; exit 1; } @command -v mvn > /dev/null 2>&1 || { echo "Maven is not installed. Please install Maven and try again."; exit 1; } @command -v java > /dev/null 2>&1 || { echo "Java is not installed. Please install Java and try again."; exit 1; } @echo "All required prerequisites are available." @@ -31,7 +30,7 @@ install: ## Deploy the infrastructure deploy: @echo "Deploying the infrastructure..." - cd terraform && tflocal init && tflocal plan && tflocal apply --auto-approve + cd terraform && lstk tf init && lstk tf plan && lstk tf apply --auto-approve @echo "Infrastructure deployed successfully." ## Test the application @@ -53,20 +52,15 @@ frontend: ## Start LocalStack in detached mode start: @test -n "${LOCALSTACK_AUTH_TOKEN}" || (echo "LOCALSTACK_AUTH_TOKEN is not set. Find your token at https://app.localstack.cloud/workspace/auth-token"; exit 1) - @LOCALSTACK_AUTH_TOKEN=$(LOCALSTACK_AUTH_TOKEN) localstack start -d + @LOCALSTACK_AUTH_TOKEN=$(LOCALSTACK_AUTH_TOKEN) lstk start ## Stop the Running LocalStack container stop: @echo - localstack stop - -## Make sure the LocalStack container is up -ready: - @echo Waiting on the LocalStack container... - @localstack wait -t 30 && echo LocalStack is ready to use! || (echo Gave up waiting on LocalStack, exiting. && exit 1) + lstk stop ## Save the logs in a separate file logs: - @localstack logs > logs.txt + @lstk logs > logs.txt -.PHONY: usage install check start ready deploy logs stop test +.PHONY: usage install check start deploy logs stop test diff --git a/README.md b/README.md index f7bb542..62439cb 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ The following diagram shows the architecture that this sample application builds ## Prerequisites - A valid [LocalStack for AWS license](https://localstack.cloud/pricing). Your license provides a [`LOCALSTACK_AUTH_TOKEN`](https://docs.localstack.cloud/getting-started/auth-token/) to activate LocalStack. -- [`localstack` CLI](https://docs.localstack.cloud/getting-started/installation/#localstack-cli). -- [AWS CLI](https://docs.localstack.cloud/user-guide/integrations/aws-cli/) with the [`awslocal` wrapper](https://docs.localstack.cloud/user-guide/integrations/aws-cli/#localstack-aws-cli-awslocal). -- [Terraform](https://docs.localstack.cloud/user-guide/integrations/terraform/) with the [`tflocal`](https://github.com/localstack/terraform-local) wrapper. +- [`lstk` CLI](https://docs.localstack.cloud/aws/tooling/lstk/). +- [AWS CLI](https://docs.localstack.cloud/user-guide/integrations/aws-cli/) with the [`lstk aws` proxy](https://docs.localstack.cloud/aws/tooling/lstk/). +- [Terraform](https://docs.localstack.cloud/user-guide/integrations/terraform/) with the [`lstk tf` proxy](https://docs.localstack.cloud/aws/tooling/lstk/). - [Maven 3.8.5+](https://maven.apache.org/install.html) & [Java 17](https://www.java.com/en/download/help/download_options.html) - [Node.js](https://nodejs.org/en/download/) & [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - [`make`](https://www.gnu.org/software/make/) (**optional**, but recommended for running the sample application) @@ -64,11 +64,10 @@ This will: ## Deployment -Start LocalStack with the `LOCALSTACK_AUTH_TOKEN` pre-configured: +Start LocalStack: ```shell -localstack auth set-token -localstack start +lstk start ``` To deploy the sample application, run the following command: @@ -138,7 +137,7 @@ Available actions: This sample demonstrates Infrastructure as Code (IaC) testing by using identical Terraform configurations for both AWS and LocalStack environments. The application leverages Spring profiles to seamlessly switch between production and development configurations without code changes. -The Terraform configuration defines all necessary AWS resources and their relationships, while `tflocal` automatically reconfigures endpoints for LocalStack. This approach enables: +The Terraform configuration defines all necessary AWS resources and their relationships, while `lstk tf` automatically reconfigures endpoints for LocalStack. This approach enables: - Validation of infrastructure changes before AWS deployment - Consistent development environments across teams @@ -167,7 +166,7 @@ This sample application demonstrates how to build, test, and deploy a full-stack - Using Spring Boot profiles to seamlessly switch between LocalStack and AWS environments. - Implementing real-time updates using Server-Sent Events and SQS message consumption. - Leveraging Testcontainers for integration testing against LocalStack infrastructure. -- Utilizing `tflocal` and `awslocal` to streamline local development workflows. +- Utilizing `lstk tf` and `lstk aws` to streamline local development workflows. ## Learn More diff --git a/pom.xml b/pom.xml index 072dfa2..abea51e 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,8 @@ 17 17 3.2.0 - 1.19.7 + 2.0.5 + 1.18.46 UTF-8 @@ -85,13 +86,13 @@ org.testcontainers - localstack + testcontainers-localstack ${testcontainers.version} test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter ${testcontainers.version} test @@ -121,6 +122,12 @@ pom import + + + org.apache.commons + commons-lang3 + 3.18.0 + software.amazon.awssdk bom diff --git a/shipment-picture-lambda-validator/pom.xml b/shipment-picture-lambda-validator/pom.xml index 7cf6904..7ebbb1b 100644 --- a/shipment-picture-lambda-validator/pom.xml +++ b/shipment-picture-lambda-validator/pom.xml @@ -57,7 +57,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.46 compile @@ -93,7 +93,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.46 diff --git a/shipment-picture-lambda-validator/src/main/resources/lambda_update_script.sh b/shipment-picture-lambda-validator/src/main/resources/lambda_update_script.sh index 9576475..b4efff1 100644 --- a/shipment-picture-lambda-validator/src/main/resources/lambda_update_script.sh +++ b/shipment-picture-lambda-validator/src/main/resources/lambda_update_script.sh @@ -1,10 +1,10 @@ -#you need awslocal cli installed for this +#you need lstk aws cli installed for this -awslocal lambda update-function-code --function-name shipment-picture-lambda-validator \ +lstk aws lambda update-function-code --function-name shipment-picture-lambda-validator \ --zip-file fileb://target/shipment-picture-lambda-validator.jar \ --region us-east-1 -aws lambda update-function-code --function-name shipment-picture-lambda-validator \ +lstk aws lambda update-function-code --function-name shipment-picture-lambda-validator \ --zip-file fileb://target/shipment-picture-lambda-validator.jar \ --region us-east-1 \ No newline at end of file diff --git a/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/LocalStackSetupConfigurations.java b/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/LocalStackSetupConfigurations.java index 6e8d87a..c7c5fe1 100644 --- a/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/LocalStackSetupConfigurations.java +++ b/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/LocalStackSetupConfigurations.java @@ -18,6 +18,7 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.Container.ExecResult; +import org.testcontainers.containers.ExecConfig; import org.testcontainers.containers.localstack.LocalStackContainer; import org.testcontainers.containers.localstack.LocalStackContainer.Service; import org.testcontainers.containers.output.Slf4jLogConsumer; @@ -68,6 +69,17 @@ public class LocalStackSetupConfigurations { private static final String LOCALSTACK_AUTH_TOKEN = System.getenv().getOrDefault( "LOCALSTACK_AUTH_TOKEN", ""); + static { + if (LOCALSTACK_AUTH_TOKEN.isBlank()) { + // fail fast here: without this, the container starts, LocalStack exits immediately for + // lack of a license, and Testcontainers still waits out its ~60s readiness timeout before + // surfacing that failure, which just looks like a hang. + throw new IllegalStateException( + "LOCALSTACK_AUTH_TOKEN is not set. Find your token at " + + "https://app.localstack.cloud/workspace/auth-token"); + } + } + @Container protected static LocalStackContainer localStack = new LocalStackContainer(DockerImageName.parse("localstack/localstack:latest")) @@ -239,12 +251,12 @@ protected static void createBucketNotificationConfiguration() // lambda needs to be in state "Active" in order to proceed with adding permissions // this can take 2-3 seconds to reach var result = localStack.execInContainer(formatCommand( - "awslocal lambda get-function --function-name shipment-picture-lambda-validator")); + "aws --endpoint-url=http://localhost:4566 lambda get-function --function-name shipment-picture-lambda-validator")); var obj = new JSONObject(result.getStdout()).getJSONObject("Configuration"); var state = obj.getString("State"); while (!state.equals("Active")) { result = localStack.execInContainer(formatCommand( - "awslocal lambda get-function --function-name shipment-picture-lambda-validator")); + "aws --endpoint-url=http://localhost:4566 lambda get-function --function-name shipment-picture-lambda-validator")); obj = new JSONObject(result.getStdout()).getJSONObject("Configuration"); state = obj.getString("State"); } @@ -440,7 +452,17 @@ protected static ExecResult executeInContainer(String command) throws Exception return execResult; } - private static String[] formatCommand(String command) { - return command.split(" "); + // lstk can't run here (it's a host-side proxy with no in-container docker/config access); + // the image already bundles the plain aws CLI, so we point it at the gateway directly instead. + private static final Map AWS_CLI_ENV = Map.of( + "AWS_ACCESS_KEY_ID", "test", + "AWS_SECRET_ACCESS_KEY", "test", + "AWS_DEFAULT_REGION", "us-east-1"); + + private static ExecConfig formatCommand(String command) { + return ExecConfig.builder() + .command(command.split(" ")) + .envVars(AWS_CLI_ENV) + .build(); } } diff --git a/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/ShipmentServiceIntegrationTest.java b/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/ShipmentServiceIntegrationTest.java index 76e214e..ab0b0ca 100644 --- a/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/ShipmentServiceIntegrationTest.java +++ b/src/test/java/dev/ancaghenade/shipmentlistdemo/integrationtests/ShipmentServiceIntegrationTest.java @@ -87,7 +87,7 @@ public String getFilename() { assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); var execResult = executeInContainer( - "awslocal s3api list-objects --bucket shipment-picture-bucket --query length(Contents[])"); + "aws --endpoint-url=http://localhost:4566 s3api list-objects --bucket shipment-picture-bucket --query length(Contents[])"); assertEquals(String.valueOf(1), execResult.getStdout().trim()); } From 6fffaab97457bfc10b2664e0d0ded52117f9a865 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Tue, 28 Jul 2026 12:16:38 +1200 Subject: [PATCH 3/3] Fix lstk config: port is required for the aws emulator CI failed with "invalid container config: port is required for aws emulator" since .lstk/config.toml omitted the port field. Also gitignore the lstk.log file lstk writes next to a --config path when run locally. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 3 +++ .lstk/config.toml | 1 + 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5bb1014..e4f2f50 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,9 @@ src/main/shipment-list-frontend/yarn-error.log* src/main/resources/buckets.properties +# lstk +.lstk/lstk.log + # terraform terraform/.terraform/ terraform/terraform.tfstate diff --git a/.lstk/config.toml b/.lstk/config.toml index 026d3bb..a06eaed 100644 --- a/.lstk/config.toml +++ b/.lstk/config.toml @@ -6,6 +6,7 @@ type = "aws" image = "localstack/localstack-pro" tag = "latest" +port = "4566" env = ["ci"] [env.ci]