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..e4f2f50 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,11 @@ 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 + +# lstk +.lstk/lstk.log + # terraform terraform/.terraform/ terraform/terraform.tfstate diff --git a/.lstk/config.toml b/.lstk/config.toml new file mode 100644 index 0000000..a06eaed --- /dev/null +++ b/.lstk/config.toml @@ -0,0 +1,13 @@ +# 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" +port = "4566" +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/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 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()); }