From 6a3cf6a71ce92deb089b350ade3d9591d83b3d6e Mon Sep 17 00:00:00 2001 From: Yuwei Zhou Date: Tue, 1 Oct 2024 06:18:21 +0800 Subject: [PATCH] Add Spring Boot PetClinic Microservices sample application (#160) * Add contribution proposal * add configuration * Update README.md * Update README.md * Add PetClinic Source * docker image build with tag * Add README.md * temp * Add service connector * Update README.md * Delete scenarios/aca-internal/bicep/sample-apps/spring-petclinic-microservices/README_PROPOSAL.md * fix comments * Add random mysql name * Add Prerequisities * Add delegate agent pool * Rename * Update config server * Update to official repo * Add description --- .../bicep/modules/02-spoke/README.md | 10 +- .../modules/03-supporting-services/README.md | 2 +- .../deploy.supporting-services.bicep | 3 + .../modules/container-registry.module.bicep | 4 + .../README.md | 31 + ...eploy.spring-petclinic-microservices.jsonc | 58 + .../docs/01-landing-zone.md | 7 + .../docs/02-container-apps.md | 100 + .../docs/03-connect-to-db.md | 103 + .../docs/04-deploy-apps.md | 90 + .../docs/hello-world.png | Bin 0 -> 39403 bytes .../main.bicep | 99 + .../manifest.yml | 47 + .../containerapp-java-components.bicep | 36 + .../modules/containerapp.bicep | 97 + .../modules/mysql.bicep | 129 + .../modules/petclinic.bicep | 99 + .../src/.gitignore | 1 + .../src/README.md | 764 ++ .../src/docker/Dockerfile | 27 + .../src/mvnw | 310 + .../src/mvnw.cmd | 182 + .../src/pom.xml | 299 + .../spring-petclinic-api-gateway/.gitignore | 1 + .../spring-petclinic-api-gateway/manifest.yml | 24 + .../src/spring-petclinic-api-gateway/pom.xml | 185 + .../petclinic/api/ApiGatewayApplication.java | 92 + .../application/CustomersServiceClient.java | 41 + .../api/application/VisitsServiceClient.java | 57 + .../boundary/web/ApiGatewayController.java | 79 + .../petclinic/api/dto/OwnerDetails.java | 52 + .../samples/petclinic/api/dto/PetDetails.java | 39 + .../samples/petclinic/api/dto/PetType.java | 27 + .../petclinic/api/dto/VisitDetails.java | 35 + .../samples/petclinic/api/dto/Visits.java | 32 + .../src/main/less/header.less | 73 + .../src/main/less/petclinic.less | 250 + .../src/main/less/responsive.less | 41 + .../src/main/less/typography.less | 60 + .../src/main/resources/application.yml | 70 + .../src/main/resources/logback-spring.xml | 6 + .../resources/messages/messages.properties | 7 + .../resources/messages/messages_de.properties | 7 + .../resources/messages/messages_en.properties | 1 + .../static/fonts/montserrat-webfont.eot | Bin 0 -> 20979 bytes .../static/fonts/montserrat-webfont.svg | 1283 +++ .../static/fonts/montserrat-webfont.ttf | Bin 0 -> 42692 bytes .../static/fonts/montserrat-webfont.woff | Bin 0 -> 24240 bytes .../static/fonts/varela_round-webfont.eot | Bin 0 -> 28963 bytes .../static/fonts/varela_round-webfont.svg | 7875 +++++++++++++++++ .../static/fonts/varela_round-webfont.ttf | Bin 0 -> 63044 bytes .../static/fonts/varela_round-webfont.woff | Bin 0 -> 32712 bytes .../main/resources/static/images/favicon.png | Bin 0 -> 528 bytes .../src/main/resources/static/images/pets.png | Bin 0 -> 67721 bytes .../resources/static/images/platform-bg.png | Bin 0 -> 9162 bytes .../images/spring-logo-dataflow-mobile.png | Bin 0 -> 3705 bytes .../static/images/spring-logo-dataflow.png | Bin 0 -> 6888 bytes .../static/images/spring-pivotal-logo.png | Bin 0 -> 2818 bytes .../src/main/resources/static/index.html | 64 + .../src/main/resources/static/scripts/app.js | 36 + .../static/scripts/fragments/footer.html | 6 + .../static/scripts/fragments/nav.html | 38 + .../static/scripts/fragments/welcome.html | 7 + .../httpErrorHandlingInterceptor.js | 17 + .../scripts/infrastructure/infrastructure.js | 3 + .../owner-details/owner-details.component.js | 7 + .../owner-details/owner-details.controller.js | 10 + .../scripts/owner-details/owner-details.js | 11 + .../owner-details/owner-details.template.html | 70 + .../owner-form/owner-form.component.js | 7 + .../owner-form/owner-form.controller.js | 30 + .../static/scripts/owner-form/owner-form.js | 16 + .../owner-form/owner-form.template.html | 39 + .../owner-list/owner-list.component.js | 7 + .../owner-list/owner-list.controller.js | 10 + .../static/scripts/owner-list/owner-list.js | 11 + .../owner-list/owner-list.template.html | 31 + .../scripts/pet-form/pet-form.component.js | 7 + .../scripts/pet-form/pet-form.controller.js | 52 + .../static/scripts/pet-form/pet-form.js | 16 + .../scripts/pet-form/pet-form.template.html | 43 + .../scripts/vet-list/vet-list.component.js | 7 + .../scripts/vet-list/vet-list.controller.js | 10 + .../static/scripts/vet-list/vet-list.js | 11 + .../scripts/vet-list/vet-list.template.html | 15 + .../static/scripts/visits/visits.component.js | 7 + .../scripts/visits/visits.controller.js | 25 + .../resources/static/scripts/visits/visits.js | 11 + .../scripts/visits/visits.template.html | 27 + .../src/main/wro/wro.properties | 4 + .../src/main/wro/wro.xml | 6 + .../api/ApiGatewayApplicationTests.java | 15 + .../VisitsServiceClientIntegrationTest.java | 64 + .../web/ApiGatewayControllerTest.java | 99 + .../web/CircuitBreakerConfiguration.java | 20 + .../src/test/jmeter/petclinic_test_plan.jmx | 568 ++ .../src/test/resources/application-test.yml | 2 + .../manifest.yml | 26 + .../pom.xml | 143 + .../CustomersServiceApplication.java | 32 + .../customers/config/MetricConfig.java | 22 + .../petclinic/customers/model/Owner.java | 125 + .../customers/model/OwnerRepository.java | 30 + .../petclinic/customers/model/Pet.java | 80 + .../customers/model/PetRepository.java | 49 + .../petclinic/customers/model/PetType.java | 47 + .../customers/web/OwnerResource.java | 90 + .../petclinic/customers/web/PetDetails.java | 50 + .../petclinic/customers/web/PetRequest.java | 40 + .../petclinic/customers/web/PetResource.java | 99 + .../web/ResourceNotFoundException.java | 13 + .../src/main/resources/application.yml | 13 + .../src/main/resources/db/hsqldb/data.sql | 31 + .../src/main/resources/db/hsqldb/schema.sql | 30 + .../src/main/resources/db/mysql/data.sql | 31 + .../src/main/resources/db/mysql/schema.sql | 31 + .../src/main/resources/logback-spring.xml | 6 + .../customers/web/PetResourceTest.java | 77 + .../src/test/resources/application-test.yml | 18 + .../spring-petclinic-vets-service/.gitignore | 1 + .../manifest.yml | 25 + .../src/spring-petclinic-vets-service/pom.xml | 162 + .../vets/VetsServiceApplication.java | 35 + .../petclinic/vets/model/Specialty.java | 48 + .../samples/petclinic/vets/model/Vet.java | 101 + .../petclinic/vets/model/VetRepository.java | 31 + .../petclinic/vets/system/CacheConfig.java | 30 + .../petclinic/vets/system/VetsProperties.java | 40 + .../petclinic/vets/web/VetResource.java | 48 + .../src/main/resources/application.yml | 16 + .../src/main/resources/db/hsqldb/data.sql | 16 + .../src/main/resources/db/hsqldb/schema.sql | 23 + .../src/main/resources/db/mysql/data.sql | 16 + .../src/main/resources/db/mysql/schema.sql | 25 + .../src/main/resources/logback-spring.xml | 6 + .../petclinic/vets/web/VetResourceTest.java | 62 + .../src/test/resources/application-test.yml | 20 + .../manifest.yml | 26 + .../spring-petclinic-visits-service/pom.xml | 142 + .../visits/VisitsServiceApplication.java | 32 + .../petclinic/visits/config/MetricConfig.java | 22 + .../samples/petclinic/visits/model/Visit.java | 67 + .../visits/model/VisitRepository.java | 38 + .../petclinic/visits/web/VisitResource.java | 79 + .../src/main/resources/application.yml | 13 + .../src/main/resources/db/hsqldb/data.sql | 4 + .../src/main/resources/db/hsqldb/schema.sql | 10 + .../src/main/resources/db/mysql/data.sql | 4 + .../src/main/resources/db/mysql/schema.sql | 12 + .../src/main/resources/logback-spring.xml | 6 + .../visits/web/VisitResourceTest.java | 61 + .../src/test/resources/application-test.yml | 18 + .../shared/bicep/container-registry.bicep | 34 + .../shared/bicep/naming/naming-rules.jsonc | 3 +- .../shared/bicep/naming/naming.module.bicep | 1 + 155 files changed, 16621 insertions(+), 3 deletions(-) create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/README.md create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/deploy.spring-petclinic-microservices.jsonc create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/01-landing-zone.md create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/02-container-apps.md create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/03-connect-to-db.md create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/04-deploy-apps.md create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/hello-world.png create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/main.bicep create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/manifest.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp-java-components.bicep create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp.bicep create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/mysql.bicep create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/petclinic.bicep create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/.gitignore create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/README.md create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/docker/Dockerfile create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw.cmd create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/pom.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/.gitignore create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/manifest.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/pom.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetType.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/Visits.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/header.less create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/petclinic.less create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/responsive.less create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/typography.less create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/application.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/logback-spring.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages.properties create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_de.properties create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_en.properties create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.eot create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.svg create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.ttf create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.woff create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/varela_round-webfont.eot create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/varela_round-webfont.svg create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/varela_round-webfont.ttf create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/varela_round-webfont.woff create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/favicon.png create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/pets.png create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/platform-bg.png create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/spring-logo-dataflow-mobile.png create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/spring-logo-dataflow.png create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/spring-pivotal-logo.png create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/index.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/app.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/footer.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/nav.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/welcome.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/httpErrorHandlingInterceptor.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/infrastructure.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.component.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.controller.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.template.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-form/owner-form.component.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-form/owner-form.controller.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-form/owner-form.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-form/owner-form.template.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-list/owner-list.component.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-list/owner-list.controller.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-list/owner-list.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-list/owner-list.template.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.component.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.controller.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.template.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/vet-list/vet-list.component.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/vet-list/vet-list.controller.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/vet-list/vet-list.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/vet-list/vet-list.template.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/visits/visits.component.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/visits/visits.controller.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/visits/visits.js create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/visits/visits.template.html create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.properties create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/ApiGatewayApplicationTests.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/application/VisitsServiceClientIntegrationTest.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayControllerTest.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/boundary/web/CircuitBreakerConfiguration.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/resources/application-test.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/manifest.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/pom.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/CustomersServiceApplication.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/config/MetricConfig.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Owner.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/OwnerRepository.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Pet.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetRepository.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetType.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetDetails.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetRequest.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/ResourceNotFoundException.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/application.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/data.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/data.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/logback-spring.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/test/resources/application-test.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/.gitignore create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/manifest.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/pom.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Specialty.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Vet.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetRepository.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/system/CacheConfig.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/system/VetsProperties.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/web/VetResource.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/application.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/data.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/schema.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/data.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/schema.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/logback-spring.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/test/java/org/springframework/samples/petclinic/vets/web/VetResourceTest.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/test/resources/application-test.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/manifest.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/pom.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/VisitsServiceApplication.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/config/MetricConfig.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/application.yml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/data.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/schema.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/data.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/schema.sql create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/logback-spring.xml create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java create mode 100644 scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/test/resources/application-test.yml diff --git a/scenarios/aca-internal/bicep/modules/02-spoke/README.md b/scenarios/aca-internal/bicep/modules/02-spoke/README.md index d17d5bef..7059cf68 100644 --- a/scenarios/aca-internal/bicep/modules/02-spoke/README.md +++ b/scenarios/aca-internal/bicep/modules/02-spoke/README.md @@ -27,6 +27,13 @@ export MSYS_NO_PATHCONV=1 :warning: You will need to get the IP address of your Azure firewall or whatever network appliance you are using and replace the `[IP OF THE NETWORK APPLIANCE] placeholder in the deploy.spoke.paramters.jsonc file with it. +1. Get the private IP address of your Azure firewall + + ```bash + NETWORK_APPLIANCE_IP_ADDRESS=$(az deployment sub show -n acalza01-hub --query properties.outputs.networkApplianceIpAddress.value -o tsv) + echo NETWORK_APPLIANCE_IP_ADDRESS: $NETWORK_APPLIANCE_IP_ADDRESS + ``` + 1. Create the regional spoke network. ```bash @@ -40,7 +47,8 @@ export MSYS_NO_PATHCONV=1 -n acalza01-spokenetwork \ -l $LOCATION \ -f 02-spoke/deploy.spoke.bicep -p 02-spoke/deploy.spoke.parameters.jsonc \ - -p hubVNetId=${RESOURCEID_VNET_HUB} + -p hubVNetId=${RESOURCEID_VNET_HUB} \ + -p networkApplianceIpAddress=${NETWORK_APPLIANCE_IP_ADDRESS} ``` 1. Explore your networking resources. *Optional.* diff --git a/scenarios/aca-internal/bicep/modules/03-supporting-services/README.md b/scenarios/aca-internal/bicep/modules/03-supporting-services/README.md index db489110..0dccf061 100644 --- a/scenarios/aca-internal/bicep/modules/03-supporting-services/README.md +++ b/scenarios/aca-internal/bicep/modules/03-supporting-services/README.md @@ -34,7 +34,7 @@ By default, they are deployed to the spoke resource group. ``` ```bash - # [This takes about four minutes to run (if you add deployRedis=false).] + # [This takes about four minutes to run (if you add deployRedisCache=false).] az deployment group create \ -n acalza01-dependencies \ -g $RESOURCENAME_RESOURCEGROUP_SPOKE \ diff --git a/scenarios/aca-internal/bicep/modules/03-supporting-services/deploy.supporting-services.bicep b/scenarios/aca-internal/bicep/modules/03-supporting-services/deploy.supporting-services.bicep index 8298aa4e..374f2d98 100644 --- a/scenarios/aca-internal/bicep/modules/03-supporting-services/deploy.supporting-services.bicep +++ b/scenarios/aca-internal/bicep/modules/03-supporting-services/deploy.supporting-services.bicep @@ -140,6 +140,9 @@ output containerRegistryLoginServer string = containerRegistry.outputs.container @description('The resource ID of the user-assigned managed identity for the Azure Container Registry to be able to pull images from it.') output containerRegistryUserAssignedIdentityId string = containerRegistry.outputs.containerRegistryUserAssignedIdentityId +@description('The name of the contianer registry agent pool name to build images') +output containerRegistryAgentPoolName string = containerRegistry.outputs.containerRegistryAgentPoolName + @description('The resource ID of the Azure Key Vault.') output keyVaultId string = keyVault.outputs.keyVaultId diff --git a/scenarios/aca-internal/bicep/modules/03-supporting-services/modules/container-registry.module.bicep b/scenarios/aca-internal/bicep/modules/03-supporting-services/modules/container-registry.module.bicep index 0a2523da..4c0f44b0 100644 --- a/scenarios/aca-internal/bicep/modules/03-supporting-services/modules/container-registry.module.bicep +++ b/scenarios/aca-internal/bicep/modules/03-supporting-services/modules/container-registry.module.bicep @@ -97,6 +97,7 @@ module containerRegistry '../../../../../shared/bicep/container-registry.bicep' publicNetworkAccess: 'Disabled' networkRuleBypassOptions: 'AzureServices' diagnosticWorkspaceId: diagnosticWorkspaceId + agentPoolSubnetId: spokePrivateEndpointSubnet.id } } @@ -147,4 +148,7 @@ output containerRegistryLoginServer string = containerRegistry.outputs.loginServ @description('The resource ID of the user assigned managed identity for the container registry to be able to pull images from it.') output containerRegistryUserAssignedIdentityId string = containerRegistryUserAssignedIdentity.id +@description('The name of Azure container registry agent pool name to build images') +output containerRegistryAgentPoolName string = containerRegistry.outputs.agentPoolName + diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/README.md b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/README.md new file mode 100644 index 00000000..25990925 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/README.md @@ -0,0 +1,31 @@ +# # Sample App: Spring Petclinic Microservices + +Spring Petclinic Microservices is a Java microservice application that simulates a pet clinic management system. + +![Spring Petclinic Microservices](docs/media/spring-petclinic-overview.png) + +The application allows users to manage information about pets, their owners, and visits to the clinic. It is built using Spring Boot and follows a microservices architecture. + +The sample app is available [here](https://github.com/azure-samples/spring-petclinic-microservices). The branch used for this scenario is [main](https://github.com/azure-samples/spring-petclinic-microservices/tree/main). + +## Overview + +The Spring Petclinic Microservices sample app consists of several microservices: + +* `customers-service` +* `vets-service` +* `visits-service` +* `api-gateway` + +Interaction/communication between these microservices is described below: + +![Services](docs/media/spring-petclinic-sequence-diagram.png) + +## Deployment + +The deployment of the sample app is done in 2 steps: + +1. :arrow_forward: [Deploy the landing zone](./docs/01-landing-zone.md) +2. :arrow_forward: [Create the container apps](./docs/02-container-apps.md) +2. :arrow_forward: [Connect the container apps with MySql DB](./docs/03-connect-to-db.md) +2. :arrow_forward: [Deploy the PetClinic microservices](./docs/04-deploy-apps.md) diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/deploy.spring-petclinic-microservices.jsonc b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/deploy.spring-petclinic-microservices.jsonc new file mode 100644 index 00000000..fb1933b3 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/deploy.spring-petclinic-microservices.jsonc @@ -0,0 +1,58 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "mysqlSubnetPrefix": { + "value": "10.1.2.64/28" + }, + "spokeVnetId": { + "value": "[SPOKE-VENT-RESOURCE-ID]" + }, + "hubVnetId": { + "value": "[HUB-VNET-RESOURCE-ID]" + }, + "subnetName": { + "value": "snet-mysql" + }, + "workloadName": { + "value": "lzaaca" + }, + //The name of the environment (e.g. "dev", "test", "prod", "preprod", "staging", "uat", "dr", "qa"). Up to 8 characters long. + "environment": { + "value": "dev" + }, + "databaseName": { + "value": "petclinic" + }, + "administratorLogin": { + "value": "azureuser" + }, + "administratorLoginPassword": { + "value": "Password123" + }, + "version": { + "value": "8.0.21" + }, + "managedEnvironmentsName": { + "value": "[ACA-ENVIRONMENT-NAME]" + }, + "configServerGitRepo": { + "value": "https://github.com/Azure-Samples/spring-petclinic-microservices-config" + }, + "configServerGitBranch": { + "value": "master" + }, + "acrIdentityId": { + "value": "[ACR-IDENTITY-RESOURCE-ID]" + }, + "acrRegistry": { + "value": "[ACR-REGISTRY-SERVER]" + }, + "simpleHelloImage": { + "value": "azuredocs/containerapps-helloworld" + }, + "simpleHelloTag": { + "value": "latest" + } + } +} \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/01-landing-zone.md b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/01-landing-zone.md new file mode 100644 index 00000000..f6ad726e --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/01-landing-zone.md @@ -0,0 +1,7 @@ +# Deploy the Landing Zone + +To deploy the Landing Zone, you can follow the complete guide in [Enterprise Scale for ACA - Private](../../../../bicep/README.md). + +The deployment of the sample app deploys also an application gateway with the same name as the one of the landing zone. It is recommended to deploy only the first four building blocks of the landing zone and then deploy the sample app, i.e. do not deploy hello world sample app and application gateway. To do so, you can set the attribute `deployHelloWorldSampleApp` to `false` in the parameters file of the landing zone. + +:arrow_forward: [Deploy Container Apps](./02-container-apps.md) diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/02-container-apps.md b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/02-container-apps.md new file mode 100644 index 00000000..d7cf11d1 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/02-container-apps.md @@ -0,0 +1,100 @@ +# Create the Container Apps with supporting services + +Once the landing zone is deployed, the container apps environment and their dependencies are already set up in the private networking environment. + +## Expected results +This documentation guides how to deploy the container apps and connect them with the created supporting services. + +## Resources + +- Azure Database for MySQL flexible server. +- Azure Container Apps Java components + - [Eureka Server](https://learn.microsoft.com/en-us/azure/container-apps/java-eureka-server?tabs=azure-cli) + - [Config Server](https://learn.microsoft.com/en-us/azure/container-apps/java-config-server?tabs=azure-cli) +- Azure Container Apps +- Connect Azure Container Apps to the Eureka Server and Config Server. +- Connect Azure Container Apps to MySQL flexible server and Azure Container Registry with User Assigned Identity. + +## Steps + +1. Go to the sample application folder as work directory + + ```bash + cd ../sample-apps/cloud-foundry-spring-boot-application + ``` + +1. Retrieve the Networking and Azure Container Registry information from previous deployment. + + ```bash + RESOURCENAME_RESOURCEGROUP_SPOKE=$(az deployment sub show -n acalza01-spokenetwork --query properties.outputs.spokeResourceGroupName.value -o tsv) + ENVIRONMENT_NAME=$(az deployment group show -n acalza01-appplat -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerAppsEnvironmentName.value -o tsv) + RESOURCEID_IDENTITY_ACR=$(az deployment group show -n acalza01-dependencies -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerRegistryUserAssignedIdentityId.value -o tsv) + REGISTRYNAME_ACR=$(az deployment group show -n acalza01-dependencies -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerRegistryName.value -o tsv) + LOGINSERVER_ACR=$(az deployment group show -n acalza01-dependencies -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerRegistryLoginServer.value -o tsv) + RESOURCEID_VNET_HUB=$(az deployment sub show -n acalza01-hub --query properties.outputs.hubVNetId.value -o tsv) + RESOURCEID_VNET_SPOKE=$(az deployment sub show -n acalza01-spokenetwork --query properties.outputs.spokeVNetId.value -o tsv) + + echo RESOURCENAME_RESOURCEGROUP_SPOKE: $RESOURCENAME_RESOURCEGROUP_SPOKE && \ + echo ENVIRONMENT_NAME: $ENVIRONMENT_NAME && \ + echo RESOURCEID_IDENTITY_ACR: $RESOURCEID_IDENTITY_ACR && \ + echo REGISTRYNAME_ACR: $REGISTRYNAME_ACR && \ + echo LOGINSERVER_ACR: $LOGINSERVER_ACR && \ + echo RESOURCEID_VNET_HUB: $RESOURCEID_VNET_HUB && \ + echo RESOURCEID_VNET_SPOKE: $RESOURCEID_VNET_SPOKE + ``` + +1. Import a wellknown hello world image to the private Azure Container Registry for further deploy. This hello world image will help to verify the resource creation. + + ```bash + az acr import -n ${REGISTRYNAME_ACR} --source mcr.microsoft.com/azuredocs/containerapps-helloworld:latest + ``` + +1. Create the desired resources + + ```bash + az deployment group create -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE \ + -f main.bicep \ + -p deploy.spring-petclinic-microservices.jsonc \ + -p spokeVnetId=${RESOURCEID_VNET_SPOKE} \ + -p hubVnetId=${RESOURCEID_VNET_HUB} \ + -p managedEnvironmentsName=${ENVIRONMENT_NAME} \ + -p acrIdentityId=${RESOURCEID_IDENTITY_ACR} \ + -p acrRegistry=${LOGINSERVER_ACR} + ``` + +1. Retrieve the FQDN of the microservices + + ```bash + FQDN=$(az deployment group show -g $RESOURCENAME_RESOURCEGROUP_SPOKE -n acalza01-appplat-java --query properties.outputs.fqdn.value -o tsv) + ``` + +1. Create the Application Gateway that connect to the microservices. Replace the environment variable `FQDN_HELLOWORLD_ACA=$FQDN` when creating the Application Gateway. Tutorial can be viewed at [06-application-gateway](../../../modules/06-application-gateway/README.md). Note to change the working directory when creating the Application Gateway. + + ```bash + cd ../../modules + ``` + +## Verification + +1. Go to the sample application folder as work directory + + ```bash + cd ../sample-apps/cloud-foundry-spring-boot-application + ``` + +1. Get the public IP of Application Gateway. + + ```bash + IP_APPGW=$(az deployment group show -g $RESOURCENAME_RESOURCEGROUP_SPOKE -n acalza01-appgw --query properties.outputs.applicationGatewayPublicIp.value -o tsv) + echo $IP_APPGW + ``` + +1. Access the "Hello World" application running in Azure Container Apps. + + Using your browser either navigate to **https://\** from above, or if you added the host file entry, to ****. *Because the cert is self-signed for this walkthrough, you will need to accept the security warnings presented by your browser.* + + ![Welcome to Azure Container Apps!](./hello-world.png) + +## Next step + +:arrow_forward: [Connect Container Apps with MySql Flexible Server](./03-connect-to-db.md) diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/03-connect-to-db.md b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/03-connect-to-db.md new file mode 100644 index 00000000..2597ad73 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/03-connect-to-db.md @@ -0,0 +1,103 @@ +# Connect Container Apps with MySql Flexible Server + +After create the Azure Container Apps and MySql Flexible Server, they are ready to be connected and work together. Since all these workloads are deployed inside a Virtual Network environment, all the operation to them should be finished inside the Virtual Network. Fortunately, we can use the jumbox we created before to operate these resources. + +## Expected results +All the container apps can successfully connect to the MySql Flexible Server. + +## Steps +1. Login to the VM using Bastion + + The username and password can be retrieved or set in [deploy.spoke.parameters.jsonc](../../../modules/02-spoke/deploy.spoke.parameters.jsonc), view `vmAdminUsername` and `vmAdminPassword`. + + ![bastion](../../../../../../docs/media/acaInternal/bastion-login.png) + +1. Install pre-reqs Azure CLI, Docker client + + Unfortunatelly the jump host doesn't have the required tools installed. So, you would have to install them. + + - [az cli](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt) + ```bash + curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + ``` + - Add necessary extensions + ```bash + az extension add --name containerapp + az extension add --name serviceconnector-passwordless --upgrade + ``` + +1. Login the Azure CLI and select current subscription. The subscription id can be retrieved from the Virtual Machine's overview blade. + + ```bash + az login --use-device-code + az account set -s + ``` + +1. Retrieve the environments + + ```bash + RESOURCENAME_RESOURCEGROUP_SPOKE=$(az deployment sub show -n acalza01-spokenetwork --query properties.outputs.spokeResourceGroupName.value -o tsv) + RESOURCEID_GATEWAY=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.apiGatewayId.value -o tsv) + RESOURCEID_CUSTOMERSERVICE=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.customerServiceId.value -o tsv) + RESOURCEID_VISITSSERVICE=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.visitsServiceId.value -o tsv) + RESOURCEID_VETSSERVICE=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.vetsServiceId.value -o tsv) + RESOURCEID_MYSQL_DATABASE=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.databaseId.value -o tsv) + RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.userAssignedIdentityClientId.value -o tsv) + RESOURCEID_MYSQL_USERASSIGNEDIDENTITY=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.userAssignedIdentity.value -o tsv) + SUBSCRIPTION_ID=$(az account show --query id -o tsv) + + echo RESOURCENAME_RESOURCEGROUP_SPOKE=$RESOURCENAME_RESOURCEGROUP_SPOKE && \ + echo RESOURCEID_GATEWAY=$RESOURCEID_GATEWAY && \ + echo RESOURCEID_CUSTOMERSERVICE=$RESOURCEID_CUSTOMERSERVICE && \ + echo RESOURCEID_VISITSSERVICE=$RESOURCEID_VISITSSERVICE && \ + echo RESOURCEID_VETSSERVICE=$RESOURCEID_VETSSERVICE && \ + echo RESOURCEID_MYSQL_DATABASE=$RESOURCEID_MYSQL_DATABASE && \ + echo RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID=$RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID && \ + echo RESOURCEID_MYSQL_USERASSIGNEDIDENTITY=$RESOURCEID_MYSQL_USERASSIGNEDIDENTITY && \ + echo SUBSCRIPTION_ID=$SUBSCRIPTION_ID + ``` + +1. Create Service Connector for Azure Container Apps and MySql Flexible Server. The below commands create users in the database and these user will be used by Azure Container Apps to connect to database. + + ```bash + az containerapp connection create mysql-flexible --connection mysql_api_gateway \ + --source-id ${RESOURCEID_GATEWAY} \ + --target-id ${RESOURCEID_MYSQL_DATABASE} \ + --client-type springBoot \ + --user-identity \ + client-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID} \ + subs-id=${SUBSCRIPTION_ID} \ + mysql-identity-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY} \ + -c api-gateway + az containerapp connection create mysql-flexible --connection mysql_customer_service \ + --source-id ${RESOURCEID_CUSTOMERSERVICE} \ + --target-id ${RESOURCEID_MYSQL_DATABASE} \ + --client-type springBoot \ + --user-identity \ + client-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID} \ + subs-id=${SUBSCRIPTION_ID} \ + mysql-identity-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY} \ + -c customer-service + az containerapp connection create mysql-flexible --connection mysql_visits_service \ + --source-id ${RESOURCEID_VISITSSERVICE} \ + --target-id ${RESOURCEID_MYSQL_DATABASE} \ + --client-type springBoot \ + --user-identity \ + client-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID} \ + subs-id=${SUBSCRIPTION_ID} \ + mysql-identity-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY} \ + -c visits-service + az containerapp connection create mysql-flexible --connection mysql_vets_service \ + --source-id ${RESOURCEID_VETSSERVICE} \ + --target-id ${RESOURCEID_MYSQL_DATABASE} \ + --client-type springBoot \ + --user-identity \ + client-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID} \ + subs-id=${SUBSCRIPTION_ID} \ + mysql-identity-id=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY} \ + -c vets-service + ``` + +## Next step + +:arrow_forward: [Deploy the Container Apps](./04-deploy-apps.md) diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/04-deploy-apps.md b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/04-deploy-apps.md new file mode 100644 index 00000000..d7b5495e --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/04-deploy-apps.md @@ -0,0 +1,90 @@ +# Deploy the Container Apps + +Once the Azure Container Apps created and connect to the database, we can go to deploy the Spring Boot application to the created apps. Every time you modify the application code, you can rerun the steps in this tutorial to deploy the changes. + +## Expected results +This documentation guides how to deploy the PetClinic microservices. + +## Resources + +- PetClinic microservices images +- Deploy images to containr apps + +## Prerequisites +- [Java 17](https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-17) +- [Maven](https://maven.apache.org/download.cgi) + +## Steps + +1. Retrieve the Networking and Azure Container Registry information from previous deployment. + + ```bash + RESOURCENAME_RESOURCEGROUP_SPOKE=$(az deployment sub show -n acalza01-spokenetwork --query properties.outputs.spokeResourceGroupName.value -o tsv) + ENVIRONMENT_NAME=$(az deployment group show -n acalza01-appplat -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerAppsEnvironmentName.value -o tsv) + RESOURCEID_IDENTITY_ACR=$(az deployment group show -n acalza01-dependencies -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerRegistryUserAssignedIdentityId.value -o tsv) + REGISTRYNAME_ACR=$(az deployment group show -n acalza01-dependencies -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerRegistryName.value -o tsv) + LOGINSERVER_ACR=$(az deployment group show -n acalza01-dependencies -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerRegistryLoginServer.value -o tsv) + RESOURCENAME_AGENTPOOL=$(az deployment group show -n acalza01-dependencies -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.containerRegistryAgentPoolName.value -o tsv) + RESOURCEID_EUREKA=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.eurekaId.value -o tsv) + RESOURCEID_CONFIGSERVER=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.configServerId.value -o tsv) + RESOURCEID_MYSQL_DATABASE=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.databaseId.value -o tsv) + RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID=$(az deployment group show -n acalza01-appplat-java -g $RESOURCENAME_RESOURCEGROUP_SPOKE --query properties.outputs.userAssignedIdentityClientId.value -o tsv) + + echo RESOURCENAME_RESOURCEGROUP_SPOKE: $RESOURCENAME_RESOURCEGROUP_SPOKE && \ + echo ENVIRONMENT_NAME: $ENVIRONMENT_NAME && \ + echo RESOURCEID_IDENTITY_ACR: $RESOURCEID_IDENTITY_ACR && \ + echo REGISTRYNAME_ACR: $REGISTRYNAME_ACR && \ + echo LOGINSERVER_ACR: $LOGINSERVER_ACR && \ + echo RESOURCENAME_AGENTPOOL: $RESOURCENAME_AGENTPOOL && \ + echo RESOURCEID_MYSQL_DATABASE: $RESOURCEID_MYSQL_DATABASE && \ + echo RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID: $RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID && \ + echo RESOURCEID_EUREKA: $RESOURCEID_EUREKA && \ + echo RESOURCEID_CONFIGSERVER: $RESOURCEID_CONFIGSERVER + ``` + +1. Go to source code folder and compile the code. + + ```bash + cd src + mvn clean package -DskipTests + cd .. + ``` + +1. Build the docker image by using Azure Container Registry Build. Each line may cost around 1 minute. + + ```bash + IMAGE_TAG=3.0.1-$(date -u +%Y%m%d%H%M%S) + + az acr build -t spring-petclinic-vets-service:${IMAGE_TAG} -r ${REGISTRYNAME_ACR} src/spring-petclinic-vets-service/target/docker --build-arg ARTIFACT_NAME=vets-service-3.0.1 --build-arg EXPOSED_PORT=8080 --agent-pool ${RESOURCENAME_AGENTPOOL} + az acr build -t spring-petclinic-visits-service:${IMAGE_TAG} -r ${REGISTRYNAME_ACR} src/spring-petclinic-visits-service/target/docker --build-arg ARTIFACT_NAME=visits-service-3.0.1 --build-arg EXPOSED_PORT=8080 --agent-pool ${RESOURCENAME_AGENTPOOL} + az acr build -t spring-petclinic-customers-service:${IMAGE_TAG} -r ${REGISTRYNAME_ACR} src/spring-petclinic-customers-service/target/docker --build-arg ARTIFACT_NAME=customers-service-3.0.1 --build-arg EXPOSED_PORT=8080 --agent-pool ${RESOURCENAME_AGENTPOOL} + az acr build -t spring-petclinic-api-gateway:${IMAGE_TAG} -r ${REGISTRYNAME_ACR} src/spring-petclinic-api-gateway/target/docker --build-arg ARTIFACT_NAME=api-gateway-3.0.1 --build-arg EXPOSED_PORT=8080 --agent-pool ${RESOURCENAME_AGENTPOOL} + ``` + +1. Deploy the microservices to Azure Container Apps + + ```bash + az deployment group create -n acalza01-appplat-microservices -g $RESOURCENAME_RESOURCEGROUP_SPOKE \ + -f modules/petclinic.bicep \ + -p managedEnvironmentsName=${ENVIRONMENT_NAME} \ + -p eurekaId=${RESOURCEID_EUREKA} \ + -p configServerId=${RESOURCEID_CONFIGSERVER} \ + -p mysqlDBId=${RESOURCEID_MYSQL_DATABASE} \ + -p mysqlUserAssignedIdentityClientId=${RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID} \ + -p acrRegistry=${LOGINSERVER_ACR} \ + -p imageTag=${IMAGE_TAG} \ + -p acrIdentityId=${RESOURCEID_IDENTITY_ACR} + ``` + +## Verification + +1. Get the public IP of Application Gateway. + + ```bash + IP_APPGW=$(az deployment group show -g $RESOURCENAME_RESOURCEGROUP_SPOKE -n acalza01-appgw --query properties.outputs.applicationGatewayPublicIp.value -o tsv) + echo $IP_APPGW + ``` + +1. Access the PetClinic application running in Azure Container Apps. + + Using your browser either navigate to **https://\** from above, or if you added the host file entry, to ****. *Because the cert is self-signed for this walkthrough, you will need to accept the security warnings presented by your browser.* diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/hello-world.png b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/hello-world.png new file mode 100644 index 0000000000000000000000000000000000000000..9dcf853920125e1e8e7e01732f41999c42462112 GIT binary patch literal 39403 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