From 50cb3abc4fd34b165b4a05f59560fdf9ea6a3237 Mon Sep 17 00:00:00 2001 From: Favee Date: Fri, 11 Apr 2025 08:35:37 +0100 Subject: [PATCH] Initial setup of CI/CD pipeline for Spring PetClinic --- .github/workflows/ci.yml | 33 ++++ .github/workflows/deploy.yml | 98 ++++++++++ .github/workflows/gradle-build.yml | 4 +- .github/workflows/maven-build.yml | 4 +- .github/workflows/performance.yml | 96 +++++++++ .github/workflows/security.yml | 86 ++++++++ .github/workflows/sonar.yml | 38 ++++ Dockerfile | 33 ++++ README.md | 226 +++++++++------------- jmeter.log | 47 +++++ performance-tests/jmeter.log | 27 +++ performance-tests/petclinic-test-plan.jmx | 203 +++++++++++++++++++ terraform/main.tf | 87 +++++++++ terraform/variables.tf | 26 +++ 14 files changed, 870 insertions(+), 138 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/performance.yml create mode 100644 .github/workflows/security.yml create mode 100644 .github/workflows/sonar.yml create mode 100644 Dockerfile create mode 100644 jmeter.log create mode 100644 performance-tests/jmeter.log create mode 100644 performance-tests/petclinic-test-plan.jmx create mode 100644 terraform/main.tf create mode 100644 terraform/variables.tf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..cf7bf7b06 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..4f3e40b17 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml index c24c121b1..74c341888 100644 --- a/.github/workflows/gradle-build.yml +++ b/.github/workflows/gradle-build.yml @@ -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: diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index a1ec4dab7..39ac435a2 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -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: diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml new file mode 100644 index 000000000..10fbfb296 --- /dev/null +++ b/.github/workflows/performance.yml @@ -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 " 500" | bc -l) )); then + echo "::error::Performance test failed: Average response time above 500ms" + exit 1 + fi + + echo "Performance test passed!" \ No newline at end of file diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 000000000..cedf26f04 --- /dev/null +++ b/.github/workflows/security.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 000000000..b3f6ea705 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..46541f8de --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/README.md b/README.md index c865c3b51..d645f0eb0 100644 --- a/README.md +++ b/README.md @@ -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 . +The following secrets need to be configured in your GitHub repository: -petclinic-screenshot +- `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:` 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 . 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. \ No newline at end of file diff --git a/jmeter.log b/jmeter.log new file mode 100644 index 000000000..f990ba289 --- /dev/null +++ b/jmeter.log @@ -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] diff --git a/performance-tests/jmeter.log b/performance-tests/jmeter.log new file mode 100644 index 000000000..c5bc9a5c3 --- /dev/null +++ b/performance-tests/jmeter.log @@ -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] diff --git a/performance-tests/petclinic-test-plan.jmx b/performance-tests/petclinic-test-plan.jmx new file mode 100644 index 000000000..c43c0db4e --- /dev/null +++ b/performance-tests/petclinic-test-plan.jmx @@ -0,0 +1,203 @@ + + + + + + false + true + false + + + + HOST + ${__P(host,localhost)} + = + + + PORT + ${__P(port,8080)} + = + + + THREADS + ${__P(threads,50)} + = + + + RAMPUP + ${__P(rampup,30)} + = + + + DURATION + ${__P(duration,120)} + = + + + + + + + + continue + + false + -1 + + ${THREADS} + ${RAMPUP} + true + ${DURATION} + + true + + + + + + + ${HOST} + ${PORT} + http + + + 6 + 5000 + 30000 + + + + + + + + + + + / + GET + true + false + true + false + + + + + + + + + + + + + + /owners/find + GET + true + false + true + false + + + + + + + + + + + + + + /vets.html + GET + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 000000000..324ba0142 --- /dev/null +++ b/terraform/main.tf @@ -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 + } +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 000000000..c16a24c35 --- /dev/null +++ b/terraform/variables.tf @@ -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 +} \ No newline at end of file