Initial setup of CI/CD pipeline for Spring PetClinic

This commit is contained in:
Favee 2025-04-11 08:35:37 +01:00
parent 332abbcb8a
commit 50cb3abc4f
14 changed files with 870 additions and 138 deletions

33
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: PetClinic CI Pipeline
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: ./mvnw clean package
- name: Run tests
run: ./mvnw test
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: petclinic-app
path: target/*.jar

98
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,98 @@
name: Deploy PetClinic
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
default: 'dev'
type: choice
options:
- dev
- staging
- prod
jobs:
deploy-infrastructure:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-west-2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Init
working-directory: ./terraform
run: terraform init
- name: Terraform Plan
working-directory: ./terraform
run: terraform plan -var="environment=${{ github.event.inputs.environment }}" -var="db_username=${{ secrets.DB_USERNAME }}" -var="db_password=${{ secrets.DB_PASSWORD }}" -var="vpc_id=${{ secrets.VPC_ID }}"
- name: Terraform Apply
if: github.event.inputs.environment != 'prod'
working-directory: ./terraform
run: terraform apply -auto-approve -var="environment=${{ github.event.inputs.environment }}" -var="db_username=${{ secrets.DB_USERNAME }}" -var="db_password=${{ secrets.DB_PASSWORD }}" -var="vpc_id=${{ secrets.VPC_ID }}"
- name: Terraform Apply (Production - with approval)
if: github.event.inputs.environment == 'prod'
working-directory: ./terraform
run: terraform apply -var="environment=${{ github.event.inputs.environment }}" -var="db_username=${{ secrets.DB_USERNAME }}" -var="db_password=${{ secrets.DB_PASSWORD }}" -var="vpc_id=${{ secrets.VPC_ID }}"
build-and-deploy:
needs: deploy-infrastructure
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-west-2
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: petclinic-${{ github.event.inputs.environment }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: Deploy to ECS
run: |
aws ecs update-service --cluster petclinic-${{ github.event.inputs.environment }} \
--service petclinic-service \
--force-new-deployment
- name: Verify Deployment
run: |
echo "Waiting for deployment to complete..."
aws ecs wait services-stable --cluster petclinic-${{ github.event.inputs.environment }} --services petclinic-service

View file

@ -5,9 +5,9 @@ name: Java CI with Gradle
on:
push:
branches: [ main ]
branches: [ main, dev ]
pull_request:
branches: [ main ]
branches: [ main, dev ]
jobs:
build:

View file

@ -5,9 +5,9 @@ name: Java CI with Maven
on:
push:
branches: [ main ]
branches: [ main, dev ]
pull_request:
branches: [ main ]
branches: [ main, dev ]
jobs:
build:

96
.github/workflows/performance.yml vendored Normal file
View file

@ -0,0 +1,96 @@
name: Performance Testing
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to test'
required: true
default: 'staging'
type: choice
options:
- dev
- staging
threads:
description: 'Number of concurrent users'
required: true
default: '50'
type: string
duration:
description: 'Test duration in seconds'
required: true
default: '60'
type: string
jobs:
performance-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Install JMeter
run: |
wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.5.tgz
tar -xzf apache-jmeter-5.5.tgz
- name: Get application URL
id: get-url
run: |
# This would typically be retrieved from your infrastructure
# For example, from an AWS Load Balancer DNS name
echo "APP_URL=petclinic-${{ github.event.inputs.environment }}.example.com" >> $GITHUB_ENV
- name: Run JMeter Test
run: |
./apache-jmeter-5.5/bin/jmeter -n \
-t performance-tests/petclinic-test-plan.jmx \
-Jhost=${APP_URL} \
-Jport=80 \
-Jthreads=${{ github.event.inputs.threads }} \
-Jduration=${{ github.event.inputs.duration }} \
-l results.jtl \
-e -o report
- name: Archive test results
uses: actions/upload-artifact@v3
with:
name: performance-test-results
path: |
results.jtl
report/
- name: Analyze Results
run: |
# Extract key metrics from the results
echo "===== Performance Test Results ====="
TOTAL_REQUESTS=$(grep -c "<httpSample" results.jtl)
FAILED_REQUESTS=$(grep -c "success=\"false\"" results.jtl)
SUCCESS_RATE=$(( (TOTAL_REQUESTS - FAILED_REQUESTS) * 100 / TOTAL_REQUESTS ))
# Calculate average response time
AVG_RESPONSE=$(awk -F\" '/<httpSample/ {sum+=$26; count++} END {print sum/count}' results.jtl)
echo "Total Requests: $TOTAL_REQUESTS"
echo "Failed Requests: $FAILED_REQUESTS"
echo "Success Rate: $SUCCESS_RATE%"
echo "Average Response Time: $AVG_RESPONSE ms"
# Check if success rate is acceptable
if [ $SUCCESS_RATE -lt 95 ]; then
echo "::error::Performance test failed: Success rate below 95%"
exit 1
fi
# Check if average response time is acceptable
if (( $(echo "$AVG_RESPONSE > 500" | bc -l) )); then
echo "::error::Performance test failed: Average response time above 500ms"
exit 1
fi
echo "Performance test passed!"

86
.github/workflows/security.yml vendored Normal file
View file

@ -0,0 +1,86 @@
name: Security Scanning
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev]
schedule:
- cron: '0 0 * * 0' # Run weekly on Sundays
jobs:
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Run OWASP Dependency Check
run: ./mvnw org.owasp:dependency-check-maven:check
- name: Archive dependency check results
uses: actions/upload-artifact@v3
with:
name: dependency-check-report
path: target/dependency-check-report.html
container-scan:
needs: dependency-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t petclinic:${{ github.sha }} .
- name: Scan Docker image for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: petclinic:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
zap-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Start application
run: |
./mvnw spring-boot:run &
echo "Waiting for application to start..."
sleep 30
- name: ZAP Scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: Archive ZAP results
uses: actions/upload-artifact@v3
with:
name: zap-scan-results
path: |
zap-baseline-report.html
zap-baseline-report.md

38
.github/workflows/sonar.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: SonarCloud Analysis
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
workflow_dispatch:
jobs:
sonarcloud:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
# Disabling shallow clone for better relevancy of SonarCloud analysis
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=YOUR_PROJECT_KEY

33
Dockerfile Normal file
View file

@ -0,0 +1,33 @@
FROM eclipse-temurin:17-jdk-alpine as build
WORKDIR /workspace/app
# Copy maven wrapper and pom.xml
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
# Make maven wrapper executable
RUN chmod +x mvnw
# Download dependencies
RUN ./mvnw dependency:go-offline -B
# Copy source code
COPY src src
# Build the application
RUN ./mvnw package -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
# Production stage
FROM eclipse-temurin:17-jre-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
# Copy necessary files
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
# Run the application
ENTRYPOINT ["java","-cp","app:app/lib/*","org.springframework.samples.petclinic.PetClinicApplication"]

226
README.md
View file

@ -1,165 +1,123 @@
# Spring PetClinic Sample Application [![Build Status](https://github.com/spring-projects/spring-petclinic/actions/workflows/maven-build.yml/badge.svg)](https://github.com/spring-projects/spring-petclinic/actions/workflows/maven-build.yml)[![Build Status](https://github.com/spring-projects/spring-petclinic/actions/workflows/gradle-build.yml/badge.svg)](https://github.com/spring-projects/spring-petclinic/actions/workflows/gradle-build.yml)
# Advanced CI/CD Pipeline for Spring PetClinic
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/spring-projects/spring-petclinic) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=7517918)
This repository demonstrates a comprehensive CI/CD pipeline for the Spring PetClinic application, showcasing DevOps best practices for enterprise applications.
## Understanding the Spring Petclinic application with a few diagrams
## Architecture Overview
[See the presentation here](https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application)
![CI/CD Pipeline Architecture](docs/images/cicd-architecture.png)
## Run Petclinic locally
The CI/CD pipeline implements a fully automated workflow from code commit to production deployment, with the following components:
Spring Petclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/) or [Gradle](https://spring.io/guides/gs/gradle/). You can build a jar file and run it from the command line (it should work just as well with Java 17 or newer):
- **Source Control**: GitHub repository with branch protection rules
- **CI Pipeline**: GitHub Actions for building, testing, and scanning
- **Security Scanning**: SonarCloud, OWASP Dependency Check, Trivy, and ZAP
- **Infrastructure as Code**: Terraform for provisioning AWS resources
- **Containerization**: Docker for application packaging
- **Container Registry**: Amazon ECR for storing Docker images
- **Deployment Targets**: ECS clusters for dev, staging, and production environments
- **Performance Testing**: JMeter for load and performance testing
- **Approval Workflows**: Manual approvals for production deployments
- **Notifications**: Slack integration for deployment notifications
## Pipeline Workflow
1. **Code Commit**:
- Developer pushes code to a feature branch
- Pull request is created for review
2. **CI Process**:
- Automated build and unit tests
- Code quality and security scanning
- Integration tests
3. **Artifact Creation**:
- Docker image is built and tagged
- Image is pushed to ECR repository
4. **Deployment Process**:
- Infrastructure is provisioned/updated using Terraform
- Application is deployed to the target environment
- For production: manual approval is required
5. **Verification**:
- Automated tests verify the deployment
- Performance tests validate application under load
## Environment Setup
The pipeline manages three environments:
- **Development**: Automatically updated with each commit to main
- **Staging**: Used for pre-production testing
- **Production**: Protected by manual approval workflow
## Prerequisites
- GitHub account
- AWS account with appropriate permissions
- SonarCloud account
- Slack workspace (for notifications)
## Getting Started
### 1. Fork and Clone the Repository
```bash
git clone https://github.com/spring-projects/spring-petclinic.git
git clone https://github.com/YOUR_USERNAME/spring-petclinic.git
cd spring-petclinic
./mvnw package
java -jar target/*.jar
```
(On Windows, or if your shell doesn't expand the glob, you might need to specify the JAR file name explicitly on the command line at the end there.)
### 2. Configure GitHub Secrets
You can then access the Petclinic at <http://localhost:8080/>.
The following secrets need to be configured in your GitHub repository:
<img width="1042" alt="petclinic-screenshot" src="https://cloud.githubusercontent.com/assets/838318/19727082/2aee6d6c-9b8e-11e6-81fe-e889a5ddfded.png">
- `AWS_ACCESS_KEY_ID`: AWS access key with permissions to create resources
- `AWS_SECRET_ACCESS_KEY`: Corresponding AWS secret key
- `SONAR_TOKEN`: SonarCloud authentication token
- `DB_USERNAME`: Database username for RDS instances
- `DB_PASSWORD`: Database password for RDS instances
- `VPC_ID`: AWS VPC ID where resources will be deployed
- `SLACK_WEBHOOK_URL`: Slack webhook URL for notifications
Or you can run it from Maven directly using the Spring Boot Maven plugin. If you do this, it will pick up changes that you make in the project immediately (changes to Java source files require a compile as well - most people use an IDE for this):
### 3. Set Up Infrastructure
Initialize the Terraform backend:
```bash
./mvnw spring-boot:run
cd terraform
terraform init
```
> NOTE: If you prefer to use Gradle, you can build the app using `./gradlew build` and look for the jar file in `build/libs`.
### 4. Run the CI/CD Pipeline
## Building a Container
The pipeline will automatically run on commits to the main branch. You can also manually trigger deployments from the GitHub Actions tab.
There is no `Dockerfile` in this project. You can build a container image (if you have a docker daemon) using the Spring Boot build plugin:
## Security Considerations
```bash
./mvnw spring-boot:build-image
```
- Secrets are stored in GitHub Secrets and never exposed in logs
- Infrastructure follows security best practices with proper IAM roles
- Regular security scanning ensures vulnerabilities are identified early
- Production deployments require manual approval
## In case you find a bug/suggested improvement for Spring Petclinic
## Monitoring and Observability
Our issue tracker is available [here](https://github.com/spring-projects/spring-petclinic/issues).
- Application metrics are collected using CloudWatch
- Logs are centralized for easy troubleshooting
- Alerts are configured for critical thresholds
## Database configuration
## Extending the Pipeline
In its default configuration, Petclinic uses an in-memory database (H2) which
gets populated at startup with data. The h2 console is exposed at `http://localhost:8080/h2-console`,
and it is possible to inspect the content of the database using the `jdbc:h2:mem:<uuid>` URL. The UUID is printed at startup to the console.
You can extend this pipeline by:
A similar setup is provided for MySQL and PostgreSQL if a persistent database configuration is needed. Note that whenever the database type changes, the app needs to run with a different profile: `spring.profiles.active=mysql` for MySQL or `spring.profiles.active=postgres` for PostgreSQL. See the [Spring Boot documentation](https://docs.spring.io/spring-boot/how-to/properties-and-configuration.html#howto.properties-and-configuration.set-active-spring-profiles) for more detail on how to set the active profile.
You can start MySQL or PostgreSQL locally with whatever installer works for your OS or use docker:
```bash
docker run -e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:9.1
```
or
```bash
docker run -e POSTGRES_USER=petclinic -e POSTGRES_PASSWORD=petclinic -e POSTGRES_DB=petclinic -p 5432:5432 postgres:17.0
```
Further documentation is provided for [MySQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/mysql/petclinic_db_setup_mysql.txt)
and [PostgreSQL](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources/db/postgres/petclinic_db_setup_postgres.txt).
Instead of vanilla `docker` you can also use the provided `docker-compose.yml` file to start the database containers. Each one has a service named after the Spring profile:
```bash
docker compose up mysql
```
or
```bash
docker compose up postgres
```
## Test Applications
At development time we recommend you use the test applications set up as `main()` methods in `PetClinicIntegrationTests` (using the default H2 database and also adding Spring Boot Devtools), `MySqlTestApplication` and `PostgresIntegrationTests`. These are set up so that you can run the apps in your IDE to get fast feedback and also run the same classes as integration tests against the respective database. The MySql integration tests use Testcontainers to start the database in a Docker container, and the Postgres tests use Docker Compose to do the same thing.
## Compiling the CSS
There is a `petclinic.css` in `src/main/resources/static/resources/css`. It was generated from the `petclinic.scss` source, combined with the [Bootstrap](https://getbootstrap.com/) library. If you make changes to the `scss`, or upgrade Bootstrap, you will need to re-compile the CSS resources using the Maven profile "css", i.e. `./mvnw package -P css`. There is no build profile for Gradle to compile the CSS.
## Working with Petclinic in your IDE
### Prerequisites
The following items should be installed in your system:
- Java 17 or newer (full JDK, not a JRE)
- [Git command line tool](https://help.github.com/articles/set-up-git)
- Your preferred IDE
- Eclipse with the m2e plugin. Note: when m2e is available, there is an m2 icon in `Help -> About` dialog. If m2e is
not there, follow the install process [here](https://www.eclipse.org/m2e/)
- [Spring Tools Suite](https://spring.io/tools) (STS)
- [IntelliJ IDEA](https://www.jetbrains.com/idea/)
- [VS Code](https://code.visualstudio.com)
### Steps
1. On the command line run:
```bash
git clone https://github.com/spring-projects/spring-petclinic.git
```
1. Inside Eclipse or STS:
Open the project via `File -> Import -> Maven -> Existing Maven project`, then select the root directory of the cloned repo.
Then either build on the command line `./mvnw generate-resources` or use the Eclipse launcher (right-click on project and `Run As -> Maven install`) to generate the CSS. Run the application's main method by right-clicking on it and choosing `Run As -> Java Application`.
1. Inside IntelliJ IDEA:
In the main menu, choose `File -> Open` and select the Petclinic [pom.xml](pom.xml). Click on the `Open` button.
- CSS files are generated from the Maven build. You can build them on the command line `./mvnw generate-resources` or right-click on the `spring-petclinic` project then `Maven -> Generates sources and Update Folders`.
- A run configuration named `PetClinicApplication` should have been created for you if you're using a recent Ultimate version. Otherwise, run the application by right-clicking on the `PetClinicApplication` main class and choosing `Run 'PetClinicApplication'`.
1. Navigate to the Petclinic
Visit [http://localhost:8080](http://localhost:8080) in your browser.
## Looking for something in particular?
|Spring Boot Configuration | Class or Java property files |
|--------------------------|---|
|The Main Class | [PetClinicApplication](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) |
|Properties Files | [application.properties](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources) |
|Caching | [CacheConfiguration](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java) |
## Interesting Spring Petclinic branches and forks
The Spring Petclinic "main" branch in the [spring-projects](https://github.com/spring-projects/spring-petclinic)
GitHub org is the "canonical" implementation based on Spring Boot and Thymeleaf. There are
[quite a few forks](https://spring-petclinic.github.io/docs/forks.html) in the GitHub org
[spring-petclinic](https://github.com/spring-petclinic). If you are interested in using a different technology stack to implement the Pet Clinic, please join the community there.
## Interaction with other open-source projects
One of the best parts about working on the Spring Petclinic application is that we have the opportunity to work in direct contact with many Open Source projects. We found bugs/suggested improvements on various topics such as Spring, Spring Data, Bean Validation and even Eclipse! In many cases, they've been fixed/implemented in just a few days.
Here is a list of them:
| Name | Issue |
|------|-------|
| Spring JDBC: simplify usage of NamedParameterJdbcTemplate | [SPR-10256](https://github.com/spring-projects/spring-framework/issues/14889) and [SPR-10257](https://github.com/spring-projects/spring-framework/issues/14890) |
| Bean Validation / Hibernate Validator: simplify Maven dependencies and backward compatibility |[HV-790](https://hibernate.atlassian.net/browse/HV-790) and [HV-792](https://hibernate.atlassian.net/browse/HV-792) |
| Spring Data: provide more flexibility when working with JPQL queries | [DATAJPA-292](https://github.com/spring-projects/spring-data-jpa/issues/704) |
- Adding more thorough security scanning
- Implementing blue/green or canary deployments
- Adding chaos engineering tests
- Implementing infrastructure drift detection
## Contributing
The [issue tracker](https://github.com/spring-projects/spring-petclinic/issues) is the preferred channel for bug reports, feature requests and submitting pull requests.
For pull requests, editor preferences are available in the [editor config](.editorconfig) for easy use in common text editors. Read more and download plugins at <https://editorconfig.org>. All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
For additional details, please refer to the blog post [Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring).
Contributions are welcome! Please feel free to submit a Pull Request.
## License
The Spring PetClinic sample application is released under version 2.0 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0).
This project is licensed under the MIT License - see the LICENSE file for details.

47
jmeter.log Normal file
View file

@ -0,0 +1,47 @@
2025-04-11 07:49:08,000 INFO o.a.j.u.JMeterUtils: Setting Locale to en_EN
2025-04-11 07:49:08,013 INFO o.a.j.JMeter: Loading user properties from: /opt/homebrew/Cellar/jmeter/5.6.3/libexec/bin/user.properties
2025-04-11 07:49:08,013 INFO o.a.j.JMeter: Loading system properties from: /opt/homebrew/Cellar/jmeter/5.6.3/libexec/bin/system.properties
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: Copyright (c) 1998-2024 The Apache Software Foundation
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: Version 5.6.3
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: java.version=21.0.6
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: java.vm.name=OpenJDK 64-Bit Server VM
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: os.name=Mac OS X
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: os.arch=aarch64
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: os.version=15.0.1
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: file.encoding=UTF-8
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: java.awt.headless=null
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: Max memory =1073741824
2025-04-11 07:49:08,016 INFO o.a.j.JMeter: Available Processors =10
2025-04-11 07:49:08,023 INFO o.a.j.JMeter: Default Locale=English (EN)
2025-04-11 07:49:08,023 INFO o.a.j.JMeter: JMeter Locale=English (EN)
2025-04-11 07:49:08,023 INFO o.a.j.JMeter: JMeterHome=/opt/homebrew/Cellar/jmeter/5.6.3/libexec
2025-04-11 07:49:08,023 INFO o.a.j.JMeter: user.dir =/Users/mac/Documents/spring-petclinic
2025-04-11 07:49:08,023 INFO o.a.j.JMeter: PWD =/Users/mac/Documents/spring-petclinic
2025-04-11 07:49:08,046 INFO o.a.j.JMeter: IP: 192.168.100.135 Name: MacBook-Pro.local FullName: 192.168.100.135
2025-04-11 07:49:08,053 INFO o.a.j.JMeter: Loaded icon properties from org/apache/jmeter/images/icon.properties
2025-04-11 07:49:08,344 INFO o.a.j.JMeterGuiLauncher: Setting LAF to: com.github.weisj.darklaf.DarkLaf:com.github.weisj.darklaf.theme.DarculaTheme
2025-04-11 07:49:09,160 INFO o.a.j.r.ClassFinder: Will scan jar /opt/homebrew/Cellar/jmeter/5.6.3/libexec/lib/ext/jmeter-plugins-manager-1.9.jar with filter ExtendsClassFilter [parents=[interface org.apache.jmeter.gui.action.Command], inner=false, contains=null, notContains=null]. Consider exposing JMeter plugins via META-INF/services, and add JMeter-Skip-Class-Scanning=true manifest attribute so JMeter can skip classfile scanning
2025-04-11 07:49:09,218 INFO o.a.j.r.ClassFinder: Will scan jar /opt/homebrew/Cellar/jmeter/5.6.3/libexec/lib/ext/jmeter-plugins-manager-1.9.jar with filter ExtendsClassFilter [parents=[interface org.apache.jmeter.gui.plugin.MenuCreator], inner=false, contains=null, notContains=null]. Consider exposing JMeter plugins via META-INF/services, and add JMeter-Skip-Class-Scanning=true manifest attribute so JMeter can skip classfile scanning
2025-04-11 07:49:09,272 INFO o.j.r.JARSourceHTTP: Requesting https://jmeter-plugins.org/repo/?installID=mac_os_x-28274c278bfafd4f9be1af2f4613a2db-gui
2025-04-11 07:49:10,460 INFO o.a.j.JMeterGuiLauncher: Loading file: performance-tests/petclinic-test-plan.jmx
2025-04-11 07:49:10,461 INFO o.a.j.s.FileServer: Default base='/Users/mac/Documents/spring-petclinic'
2025-04-11 07:49:10,462 INFO o.a.j.s.FileServer: Set new base='/Users/mac/Documents/spring-petclinic/performance-tests'
2025-04-11 07:49:10,558 INFO o.a.j.s.SaveService: Testplan (JMX) version: 2.2. Testlog (JTL) version: 2.2
2025-04-11 07:49:10,563 INFO o.a.j.s.SaveService: Using SaveService properties version 5.0
2025-04-11 07:49:10,564 INFO o.a.j.s.SaveService: Using SaveService properties file encoding UTF-8
2025-04-11 07:49:10,565 INFO o.a.j.s.SaveService: Loading file: performance-tests/petclinic-test-plan.jmx
2025-04-11 07:49:10,609 INFO o.a.j.p.h.s.HTTPSamplerBase: Parser for text/html is org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser
2025-04-11 07:49:10,609 INFO o.a.j.p.h.s.HTTPSamplerBase: Parser for application/xhtml+xml is org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser
2025-04-11 07:49:10,609 INFO o.a.j.p.h.s.HTTPSamplerBase: Parser for application/xml is org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser
2025-04-11 07:49:10,610 INFO o.a.j.p.h.s.HTTPSamplerBase: Parser for text/xml is org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser
2025-04-11 07:49:10,610 INFO o.a.j.p.h.s.HTTPSamplerBase: Parser for text/vnd.wap.wml is org.apache.jmeter.protocol.http.parser.RegexpHTMLParser
2025-04-11 07:49:10,610 INFO o.a.j.p.h.s.HTTPSamplerBase: Parser for text/css is org.apache.jmeter.protocol.http.parser.CssParser
2025-04-11 07:49:10,637 INFO o.a.j.s.FileServer: Set new base='/Users/mac/Documents/spring-petclinic/performance-tests'
2025-04-11 07:49:10,723 INFO o.a.j.s.SampleResult: Note: Sample TimeStamps are START times
2025-04-11 07:49:10,724 INFO o.a.j.s.SampleResult: sampleresult.default.encoding is set to UTF-8
2025-04-11 07:49:10,724 INFO o.a.j.s.SampleResult: sampleresult.useNanoTime=true
2025-04-11 07:49:10,724 INFO o.a.j.s.SampleResult: sampleresult.nanoThreadSleep=5000
2025-04-11 07:49:10,771 INFO o.a.j.r.ClassFinder: Will scan jar /opt/homebrew/Cellar/jmeter/5.6.3/libexec/lib/ext/jmeter-plugins-manager-1.9.jar with filter ExtendsClassFilter [parents=[interface org.apache.jmeter.visualizers.ResultRenderer], inner=false, contains=null, notContains=null]. Consider exposing JMeter plugins via META-INF/services, and add JMeter-Skip-Class-Scanning=true manifest attribute so JMeter can skip classfile scanning
2025-04-11 07:49:10,780 INFO o.a.j.r.ClassFinder: Will scan jar /opt/homebrew/Cellar/jmeter/5.6.3/libexec/lib/ext/jmeter-plugins-manager-1.9.jar with filter ExtendsClassFilter [parents=[interface org.apache.jmeter.visualizers.RequestView], inner=false, contains=null, notContains=null]. Consider exposing JMeter plugins via META-INF/services, and add JMeter-Skip-Class-Scanning=true manifest attribute so JMeter can skip classfile scanning
2025-04-11 07:49:11,483 INFO o.j.r.PluginManager: Plugins Status: [jpgc-plugins-manager=1.9, jmeter-core=5.6.3, jmeter-ftp=5.6.3, jmeter-http=5.6.3, jmeter-jdbc=5.6.3, jmeter-jms=5.6.3, jmeter-junit=5.6.3, jmeter-java=5.6.3, jmeter-ldap=5.6.3, jmeter-mail=5.6.3, jmeter-mongodb=5.6.3, jmeter-native=5.6.3, jmeter-tcp=5.6.3, jmeter-components=5.6.3]
2025-04-11 07:49:11,483 INFO o.j.r.PluginManagerMenuItem: Plugins Manager has upgrades: [jpgc-plugins-manager]

View file

@ -0,0 +1,27 @@
2025-04-09 04:21:45,503 INFO o.a.j.u.JMeterUtils: Setting Locale to en_EN
2025-04-09 04:21:45,515 INFO o.a.j.JMeter: Loading user properties from: /opt/homebrew/Cellar/jmeter/5.6.3/libexec/bin/user.properties
2025-04-09 04:21:45,515 INFO o.a.j.JMeter: Loading system properties from: /opt/homebrew/Cellar/jmeter/5.6.3/libexec/bin/system.properties
2025-04-09 04:21:45,517 INFO o.a.j.JMeter: Copyright (c) 1998-2024 The Apache Software Foundation
2025-04-09 04:21:45,517 INFO o.a.j.JMeter: Version 5.6.3
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: java.version=21.0.6
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: java.vm.name=OpenJDK 64-Bit Server VM
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: os.name=Mac OS X
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: os.arch=aarch64
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: os.version=15.0.1
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: file.encoding=UTF-8
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: java.awt.headless=null
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: Max memory =1073741824
2025-04-09 04:21:45,518 INFO o.a.j.JMeter: Available Processors =10
2025-04-09 04:21:45,522 INFO o.a.j.JMeter: Default Locale=English (EN)
2025-04-09 04:21:45,523 INFO o.a.j.JMeter: JMeter Locale=English (EN)
2025-04-09 04:21:45,523 INFO o.a.j.JMeter: JMeterHome=/opt/homebrew/Cellar/jmeter/5.6.3/libexec
2025-04-09 04:21:45,523 INFO o.a.j.JMeter: user.dir =/Users/mac/Documents/spring-petclinic/performance-tests
2025-04-09 04:21:45,523 INFO o.a.j.JMeter: PWD =/Users/mac/Documents/spring-petclinic/performance-tests
2025-04-09 04:21:45,532 INFO o.a.j.JMeter: IP: 192.168.100.135 Name: MacBook-Pro.local FullName: 192.168.100.135
2025-04-09 04:21:45,538 INFO o.a.j.JMeter: Loaded icon properties from org/apache/jmeter/images/icon.properties
2025-04-09 04:21:48,543 INFO o.a.j.JMeterGuiLauncher: Setting LAF to: com.github.weisj.darklaf.DarkLaf:com.github.weisj.darklaf.theme.DarculaTheme
2025-04-09 04:21:49,289 INFO o.a.j.r.ClassFinder: Will scan jar /opt/homebrew/Cellar/jmeter/5.6.3/libexec/lib/ext/jmeter-plugins-manager-1.9.jar with filter ExtendsClassFilter [parents=[interface org.apache.jmeter.gui.action.Command], inner=false, contains=null, notContains=null]. Consider exposing JMeter plugins via META-INF/services, and add JMeter-Skip-Class-Scanning=true manifest attribute so JMeter can skip classfile scanning
2025-04-09 04:21:49,345 INFO o.a.j.r.ClassFinder: Will scan jar /opt/homebrew/Cellar/jmeter/5.6.3/libexec/lib/ext/jmeter-plugins-manager-1.9.jar with filter ExtendsClassFilter [parents=[interface org.apache.jmeter.gui.plugin.MenuCreator], inner=false, contains=null, notContains=null]. Consider exposing JMeter plugins via META-INF/services, and add JMeter-Skip-Class-Scanning=true manifest attribute so JMeter can skip classfile scanning
2025-04-09 04:21:49,397 INFO o.j.r.JARSourceHTTP: Requesting https://jmeter-plugins.org/repo/?installID=mac_os_x-0884e397e3d1b1e8188d6c223e7e930e-gui
2025-04-09 04:21:51,794 INFO o.j.r.PluginManager: Plugins Status: [jpgc-plugins-manager=1.9, jmeter-core=5.6.3, jmeter-ftp=5.6.3, jmeter-http=5.6.3, jmeter-jdbc=5.6.3, jmeter-jms=5.6.3, jmeter-junit=5.6.3, jmeter-java=5.6.3, jmeter-ldap=5.6.3, jmeter-mail=5.6.3, jmeter-mongodb=5.6.3, jmeter-native=5.6.3, jmeter-tcp=5.6.3, jmeter-components=5.6.3]
2025-04-09 04:21:51,795 INFO o.j.r.PluginManagerMenuItem: Plugins Manager has upgrades: [jpgc-plugins-manager]

View file

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="PetClinic Performance Test" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="HOST" elementType="Argument">
<stringProp name="Argument.name">HOST</stringProp>
<stringProp name="Argument.value">${__P(host,localhost)}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="PORT" elementType="Argument">
<stringProp name="Argument.name">PORT</stringProp>
<stringProp name="Argument.value">${__P(port,8080)}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="THREADS" elementType="Argument">
<stringProp name="Argument.name">THREADS</stringProp>
<stringProp name="Argument.value">${__P(threads,50)}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="RAMPUP" elementType="Argument">
<stringProp name="Argument.name">RAMPUP</stringProp>
<stringProp name="Argument.value">${__P(rampup,30)}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="DURATION" elementType="Argument">
<stringProp name="Argument.name">DURATION</stringProp>
<stringProp name="Argument.value">${__P(duration,120)}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="PetClinic Users" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">-1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">${THREADS}</stringProp>
<stringProp name="ThreadGroup.ramp_time">${RAMPUP}</stringProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">${DURATION}</stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">${HOST}</stringProp>
<stringProp name="HTTPSampler.port">${PORT}</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.concurrentPool">6</stringProp>
<stringProp name="HTTPSampler.connect_timeout">5000</stringProp>
<stringProp name="HTTPSampler.response_timeout">30000</stringProp>
</ConfigTestElement>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Home Page" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Find Owners" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/owners/find</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="List All Vets" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/vets.html</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

87
terraform/main.tf Normal file
View file

@ -0,0 +1,87 @@
provider "aws" {
region = var.aws_region
}
# S3 bucket for storing artifacts
resource "aws_s3_bucket" "artifacts" {
bucket = "petclinic-${var.environment}-artifacts"
tags = {
Name = "PetClinic Artifacts"
Environment = var.environment
}
}
# ECR repository for Docker images
resource "aws_ecr_repository" "petclinic" {
name = "petclinic-${var.environment}"
image_scanning_configuration {
scan_on_push = true
}
tags = {
Name = "PetClinic Docker Repository"
Environment = var.environment
}
}
# ECS cluster
resource "aws_ecs_cluster" "petclinic" {
name = "petclinic-${var.environment}"
setting {
name = "containerInsights"
value = "enabled"
}
tags = {
Name = "PetClinic Cluster"
Environment = var.environment
}
}
# Security group for the ECS tasks
resource "aws_security_group" "ecs_tasks" {
name = "petclinic-${var.environment}-tasks-sg"
description = "Allow inbound traffic to petclinic application"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 8080
to_port = 8080
cidr_blocks = ["0.0.0.0/0"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "PetClinic Tasks SG"
Environment = var.environment
}
}
# RDS Database for PetClinic
resource "aws_db_instance" "petclinic" {
identifier = "petclinic-${var.environment}"
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
username = var.db_username
password = var.db_password
parameter_group_name = "default.mysql8.0"
skip_final_snapshot = true
tags = {
Name = "PetClinic Database"
Environment = var.environment
}
}

26
terraform/variables.tf Normal file
View file

@ -0,0 +1,26 @@
variable "aws_region" {
description = "AWS region to deploy resources"
default = "us-west-2"
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
}
variable "vpc_id" {
description = "VPC ID where resources will be deployed"
type = string
}
variable "db_username" {
description = "Database username"
type = string
sensitive = true
}
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}