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 00000000..9dcf8539 Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/docs/hello-world.png differ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/main.bicep b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/main.bicep new file mode 100644 index 00000000..1eb0e3f0 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/main.bicep @@ -0,0 +1,99 @@ +param administratorLogin string +@secure() +param administratorLoginPassword string +param databaseName string +param version string +param mysqlSubnetPrefix string +param spokeVnetId string +param hubVnetId string +param subnetName string + +param managedEnvironmentsName string +param configServerGitRepo string +param configServerGitBranch string + +param acrIdentityId string +param acrRegistry string +param simpleHelloImage string +param simpleHelloTag string + +@description('The name of the workload that is being deployed. Up to 10 characters long.') +@minLength(2) +@maxLength(10) +param workloadName string + +@description('The name of the environment (e.g. "dev", "test", "prod", "uat", "dr", "qa"). Up to 8 characters long.') +@maxLength(8) +param environment string + +@description('The location where the resources will be created. This needs to be the same region as the spoke.') +param location string = resourceGroup().location + + +@description('User-configured naming rules') +module naming '../../../../shared/bicep/naming/naming.module.bicep' = { + name: take('04-sharedNamingDeployment-${deployment().name}', 64) + params: { + uniqueId: uniqueString(resourceGroup().id) + environment: environment + workloadName: workloadName + location: location + } +} + +module mysql 'modules/mysql.bicep' = { + name: 'mysql' + params: { + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + serverName: naming.outputs.resourcesNames.mysqlServer + databaseName: databaseName + version: version + spokeVnetId: spokeVnetId + hubVnetId: hubVnetId + subnetName: subnetName + mysqlSubnetPrefix: mysqlSubnetPrefix + } +} + +module javaComponents 'modules/containerapp-java-components.bicep' = { + name: 'javaComponents' + params: { + managedEnvironmentsName: managedEnvironmentsName + configServerGitRepo: configServerGitRepo + configServerGitBranch: configServerGitBranch + } +} + +module applications 'modules/petclinic.bicep' = { + name: 'petclinic-microservices' + params: { + managedEnvironmentsName: managedEnvironmentsName + eurekaId: javaComponents.outputs.eurekaId + configServerId: javaComponents.outputs.configServerId + mysqlDBId: mysql.outputs.databaseId + mysqlUserAssignedIdentityClientId: mysql.outputs.userAssignedIdentityClientId + acrRegistry: acrRegistry + acrIdentityId: acrIdentityId + imageTag: simpleHelloTag + /* Set the first creation to hello world image */ + apiGatewayImage: simpleHelloImage + customerServiceImage: simpleHelloImage + vetsServiceImage: simpleHelloImage + visitsServiceImage: simpleHelloImage + targetPort: 80 + } +} + +output fqdn string = applications.outputs.fqdn + +output apiGatewayId string = applications.outputs.gatewayId +output customerServiceId string = applications.outputs.customerServiceId +output visitsServiceId string = applications.outputs.visitsServiceId +output vetsServiceId string = applications.outputs.vetsServiceId + +output eurekaId string = javaComponents.outputs.eurekaId +output configServerId string = javaComponents.outputs.configServerId +output databaseId string = mysql.outputs.databaseId +output userAssignedIdentityClientId string = mysql.outputs.userAssignedIdentityClientId +output userAssignedIdentity string = mysql.outputs.userAssignedIdentity diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/manifest.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/manifest.yml new file mode 100644 index 00000000..a5b2b2eb --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/manifest.yml @@ -0,0 +1,47 @@ +defaults: &defaults + buildpacks: + - java_buildpack_offline + memory: 1G + health-check-type: port + health-check-invocation-timeout: 30 + env: + JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 17.+ }}' + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}' + # Required to override SERVER_PORT env var set by buildpack and load + # default Spring application on port 8081 + # SPRING_APPLICATION_JSON: '{"server.port":8080}' + SPRING_PROFILES_ACTIVE: mysql + SERVER_PORT: 8080 + EUREKA_INSTANCE_SECURE_PORT_ENABLED: false + EUREKA_INSTANCE_NON_SECURE_PORT: 8080 + EUREKA_INSTANCE_PREFER_IP_ADDRESS: true + services: + - petclinic-service-registry + - petclinic-config-server + - petclinic-mysql + +applications: +- name: spring-petclinic-api-gateway + <<: *defaults + path: spring-petclinic-api-gateway/target/api-gateway-3.0.1.jar + routes: + - route: spring-petclinic-api-gateway.petclinic.xiading-tanzu-tas.azdmss-test.net + protocol: http1 +- name: spring-petclinic-customers-service + <<: *defaults + path: spring-petclinic-customers-service/target/customers-service-3.0.1.jar + routes: + - route: spring-petclinic-customers-service.petclinic.internal + protocol: http1 +- name: spring-petclinic-vets-service + <<: *defaults + path: spring-petclinic-vets-service/target/vets-service-3.0.1.jar + routes: + - route: spring-petclinic-vets-service.petclinic.internal + protocol: http1 +- name: spring-petclinic-visits-service + <<: *defaults + path: spring-petclinic-visits-service/target/visits-service-3.0.1.jar + routes: + - route: spring-petclinic-visits-service.petclinic.internal + protocol: http1 \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp-java-components.bicep b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp-java-components.bicep new file mode 100644 index 00000000..2f5742e8 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp-java-components.bicep @@ -0,0 +1,36 @@ +param managedEnvironmentsName string +param configServerGitRepo string +param configServerGitBranch string + +resource managedEnvironmentsResource 'Microsoft.App/managedEnvironments@2024-03-01' existing = { + name: managedEnvironmentsName +} + +resource configServer 'Microsoft.App/managedEnvironments/javaComponents@2024-02-02-preview' = { + parent: managedEnvironmentsResource + name: 'configserver' + properties: { + componentType: 'SpringCloudConfig' + configurations: [ + { + propertyName: 'spring.cloud.config.server.git.uri' + value: configServerGitRepo + } + { + propertyName: 'spring.cloud.config.server.git.default-label' + value: configServerGitBranch + } + ] + } +} + +resource eureka 'Microsoft.App/managedEnvironments/javaComponents@2024-02-02-preview' = { + parent: managedEnvironmentsResource + name: 'eureka' + properties: { + componentType: 'SpringCloudEureka' + } +} + +output eurekaId string = eureka.id +output configServerId string = configServer.id diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp.bicep b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp.bicep new file mode 100644 index 00000000..58727462 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/containerapp.bicep @@ -0,0 +1,97 @@ +param location string +param managedEnvironmentId string +param registry string = 'mcr.microsoft.com' +param image string = 'k8se/quickstart' +param appName string +param eurekaId string +param configServerId string +param external bool = false +param containerRegistryUserAssignedIdentityId string +param mysqlDBId string +param mysqlUserAssignedIdentityClientId string +param targetPort int + +resource app 'Microsoft.App/containerApps@2024-02-02-preview' = { + name: appName + location: location + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${containerRegistryUserAssignedIdentityId}': {} + } + } + properties: { + managedEnvironmentId: managedEnvironmentId + configuration: { + ingress:{ + external: external + targetPort: targetPort + } + registries: containerRegistryUserAssignedIdentityId == null ? null : [ + { + server: registry + identity: containerRegistryUserAssignedIdentityId + } + ] + } + template: { + containers: [ + { + image: '${registry}/${image}' + imageType: 'ContainerImage' + name: appName + env: [ + { + name: 'SPRING_PROFILES_ACTIVE' + value: 'passwordless' + } + ] + resources: { + cpu: 1 + memory: '2Gi' + } + } + ] + scale: { + minReplicas: 1 + maxReplicas: 5 + } + serviceBinds: [ + { + serviceId: eurekaId + name: 'eureka' + } + { + serviceId: configServerId + name: 'configserver' + } + ] + } + } +} + +var mysqlToken = !empty(mysqlDBId) ? split(mysqlDBId, '/') : array('') +var mysqlSubscriptionId = mysqlToken[2] +var normalizedAppName = replace(appName, '-', '_') + +resource connectDB 'Microsoft.ServiceLinker/linkers@2023-04-01-preview' = { + name: 'mysql_${normalizedAppName}' + scope: app + properties: { + scope: appName + clientType: 'springBoot' + authInfo: { + authType: 'userAssignedIdentity' + clientId: mysqlUserAssignedIdentityClientId + subscriptionId: mysqlSubscriptionId + userName: 'aad_mysql_${normalizedAppName}' + } + targetService: { + type: 'AzureResource' + id: mysqlDBId + } + } +} + +output appId string = app.id +output appFqdn string = app.properties.configuration.ingress != null ? app.properties.configuration.ingress.fqdn : '' diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/mysql.bicep b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/mysql.bicep new file mode 100644 index 00000000..0071143c --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/mysql.bicep @@ -0,0 +1,129 @@ +targetScope = 'resourceGroup' + +@description('The location where the resources will be created.') +param location string = resourceGroup().location + +param administratorLogin string +@secure() +param administratorLoginPassword string + +param serverName string +param databaseName string +param version string +param tags object = {} + +param mysqlSubnetPrefix string + +@description('The id of the spoke VNet to which the private endpoint will be connected.') +param spokeVnetId string +param subnetName string + +@description('The resource ID of the existing hub virtual network.') +param hubVnetId string + +var vnetTokens = !empty(spokeVnetId) ? split(spokeVnetId, '/') : array('') +var vnetName = vnetTokens[8] + +var hubVnetTokens = !empty(hubVnetId) ? split(hubVnetId, '/') : array('') +var hubResourceGroupName = hubVnetTokens[4] +var hubSubscriptionId = hubVnetTokens[2] +var hubVnetName = hubVnetTokens[8] + +resource mysqlUserAssignedIdentityRW 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: 'lzaamysqluserassignedidentity-rw' + location: location +} + +resource mysqlUserAssignedIdentityAdmin 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: 'lzaamysqluserassignedidentity-admin' + location: location +} + +@description('The Private DNS zone containing the mysql IP') +module privateDnsZone '../../../../../shared/bicep/network/private-dns-zone.bicep' = { + scope: resourceGroup(hubSubscriptionId, hubResourceGroupName) + name: 'mysqlPrivateDnsZone-${uniqueString(resourceGroup().id)}' + params: { + name: '${serverName}.private.mysql.database.azure.com' + virtualNetworkLinks: [ + { + vnetName: vnetName /* Link to spoke */ + vnetId: spokeVnetId + registrationEnabled: false + } + { + vnetName: hubVnetName /* Link to hub */ + vnetId: hubVnetId + registrationEnabled: false + } + ] + tags: tags + } +} + + +resource mysqlSubnet 'Microsoft.Network/virtualNetworks/subnets@2022-07-01' = { + name: '${vnetName}/${subnetName}' + properties: { + addressPrefix: mysqlSubnetPrefix + delegations: [ + { + name: 'MySQLflexibleServers' + properties: { + serviceName: 'Microsoft.DBforMySQL/flexibleServers' + } + } + ] + } +} + +resource server 'Microsoft.DBforMySQL/flexibleServers@2021-12-01-preview' = { + name: serverName + location: location + tags: tags + sku: { + name: 'Standard_B1ms' + tier: 'Burstable' + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${mysqlUserAssignedIdentityRW.id}': {} + } + } + properties: { + createMode: 'Default' + version: version + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + network: { + delegatedSubnetResourceId: mysqlSubnet.id + privateDnsZoneResourceId: privateDnsZone.outputs.privateDnsZonesId + } + storage: { + storageSizeGB: 20 + iops: 360 + autoGrow: 'Enabled' + } + backup: { + backupRetentionDays: 7 + geoRedundantBackup: 'Disabled' + } + highAvailability: { + mode: 'Disabled' + } + } +} + +resource database 'Microsoft.DBforMySQL/flexibleServers/databases@2021-12-01-preview' = { + parent: server + name: databaseName + properties: { + charset: 'utf8' + collation: 'utf8_general_ci' + } +} + +output databaseId string = database.id +output userAssignedIdentityClientId string = mysqlUserAssignedIdentityRW.properties.clientId +output userAssignedIdentity string = mysqlUserAssignedIdentityRW.id diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/petclinic.bicep b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/petclinic.bicep new file mode 100644 index 00000000..63328035 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/petclinic.bicep @@ -0,0 +1,99 @@ +param managedEnvironmentsName string +param eurekaId string +param configServerId string + +param mysqlDBId string +param mysqlUserAssignedIdentityClientId string + +param imageTag string +param acrRegistry string +param acrIdentityId string + +param apiGatewayImage string = 'spring-petclinic-api-gateway' +param customerServiceImage string = 'spring-petclinic-customers-service' +param vetsServiceImage string = 'spring-petclinic-vets-service' +param visitsServiceImage string = 'spring-petclinic-visits-service' + +param targetPort int = 8080 + +resource environment 'Microsoft.App/managedEnvironments@2024-03-01' existing = { + name: managedEnvironmentsName +} + +module apiGateway 'containerapp.bicep' = { + name: 'api-gateway' + params: { + location: environment.location + managedEnvironmentId: environment.id + appName: 'api-gateway' + eurekaId: eurekaId + configServerId: configServerId + registry: acrRegistry + image: '${apiGatewayImage}:${imageTag}' + containerRegistryUserAssignedIdentityId: acrIdentityId + external: true + targetPort: targetPort + mysqlDBId: mysqlDBId + mysqlUserAssignedIdentityClientId: mysqlUserAssignedIdentityClientId + } +} + +module customerService 'containerapp.bicep' = { + name: 'customer-service' + params: { + location: environment.location + managedEnvironmentId: environment.id + appName: 'customer-service' + eurekaId: eurekaId + configServerId: configServerId + registry: acrRegistry + image: '${customerServiceImage}:${imageTag}' + containerRegistryUserAssignedIdentityId: acrIdentityId + external: false + targetPort: targetPort + mysqlDBId: mysqlDBId + mysqlUserAssignedIdentityClientId: mysqlUserAssignedIdentityClientId + } +} + +module vetsService 'containerapp.bicep' = { + name: 'vets-service' + params: { + location: environment.location + managedEnvironmentId: environment.id + appName: 'vets-service' + eurekaId: eurekaId + configServerId: configServerId + registry: acrRegistry + image: '${vetsServiceImage}:${imageTag}' + containerRegistryUserAssignedIdentityId: acrIdentityId + external: false + targetPort: targetPort + mysqlDBId: mysqlDBId + mysqlUserAssignedIdentityClientId: mysqlUserAssignedIdentityClientId + } +} + +module visitsService 'containerapp.bicep' = { + name: 'visits-service' + params: { + location: environment.location + managedEnvironmentId: environment.id + appName: 'visits-service' + eurekaId: eurekaId + configServerId: configServerId + registry: acrRegistry + image: '${visitsServiceImage}:${imageTag}' + containerRegistryUserAssignedIdentityId: acrIdentityId + external: false + targetPort: targetPort + mysqlDBId: mysqlDBId + mysqlUserAssignedIdentityClientId: mysqlUserAssignedIdentityClientId + } +} + +output fqdn string = apiGateway.outputs.appFqdn +output gatewayId string = apiGateway.outputs.appId +output customerServiceId string = customerService.outputs.appId +output vetsServiceId string = vetsService.outputs.appId +output visitsServiceId string = visitsService.outputs.appId diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/.gitignore b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/.gitignore new file mode 100644 index 00000000..9f970225 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/README.md b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/README.md new file mode 100644 index 00000000..49713583 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/README.md @@ -0,0 +1,764 @@ +--- +page_type: sample +languages: +- azdeveloper +- java +- bicep +products: +- azure +- Azure Spring Apps +- ms-build-openjdk +name: "Deploy Spring Boot apps using Azure Spring Apps" +description: "Deploy Spring Boot apps using Azure Spring Apps" +urlFragment: "spring-petclinic-microservices" +--- + +# Deploy Spring Boot apps using Azure Spring Apps and MySQL + +This sample is an AZD template for a Spring Boot Pet Clinic application split into microservices running on Azure Spring Apps. + +Let's jump in and get this up and running in Azure. When you are finished, you will have a fully functional Spring Petclinic application deployed to the cloud. In later steps, you'll see how to setup a pipeline and run the application. + +![Spring Petclinic Application](media/petclinic.jpg) + +Before delving into the step-by-step execution of the application, you can simply click the Deploy to Azure button. This will instantly deploy the app to Azure Spring Apps. + +| Deploy to Azure Spring Apps | | +|--|--| +| Consumption plan |Not supported| +| Basic/Standard plan|Not supported| +| Enterprise plan |[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Fspring-petclinic-microservices%2Fazure%2Finfra%2Fazuredeploy.json)| + +## Run with AZD + +### Prerequisites + +The following prerequisites are required to use this application. Please ensure that you have them all installed locally. + +- [Azure Developer CLI 1.2.0 or later](https://aka.ms/azd-install) +- [Java 17 or later](https://learn.microsoft.com/java/openjdk/install) - for API backend +- [Powershell 7](https://learn.microsoft.com/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3) if you use windows + +If you are using Azure Developer CLI with the version lower than 1.2.0, then you will need to enable the feature for Azure Spring Apps support manually by the following command: +```bash +azd config set alpha.springapp on +``` + +### Quickstart + +To learn how to get started with any template, follow the steps in [this quickstart](https://learn.microsoft.com/azure/developer/azure-developer-cli/get-started?tabs=localinstall&pivots=programming-language-java) with this template(`Azure-Samples/spring-petclinic-microservices`). + +This quickstart will show you how to authenticate on Azure, initialize using a template, provision infrastructure and deploy code on Azure via the following commands: + +```bash +# Log in to azd. Only required once per-install. +azd auth login + +# First-time project setup. Initialize a project in the current directory, using this template. +azd init --template Azure-Samples/spring-petclinic-microservices + +# Provision and deploy to Azure +azd up +``` + +### Application Architecture + +This application utilizes the following Azure resources: + +- [**Azure Spring Apps**](https://docs.microsoft.com/azure/spring-apps/) to host the application + +The Architecture diagram of the Spring Petclinic Microservices is shown as below: + +![Spring Petclinic Microservices architecture](docs/microservices-architecture-diagram.jpg) + +> This template provisions resources to an Azure subscription that you will select upon provisioning them. Please refer to the [Pricing calculator for Microsoft Azure](https://azure.microsoft.com/pricing/calculator/) and, if needed, update the included Azure resource definitions found in `infra/main.bicep` to suit your needs. + +### Application Code + +This template is structured to follow the [Azure Developer CLI](https://aka.ms/azure-dev/overview). You can learn more about `azd` architecture in [the official documentation](https://learn.microsoft.com/azure/developer/azure-developer-cli/make-azd-compatible?pivots=azd-create#understand-the-azd-architecture). + +### Additional `azd` commands + +At this point, you have a complete application deployed on Azure. But there is much more that the Azure Developer CLI can do. These next steps will introduce you to additional commands that will make creating applications on Azure much easier. Using the Azure Developer CLI, you can delete the resources easily. + +- [`azd down`](https://learn.microsoft.com/azure/developer/azure-developer-cli/reference#azd-down) - to delete all the Azure resources created with this template + +The Azure Developer CLI includes many other commands to help with your Azure development experience. You can view these commands at the terminal by running `azd help`. You can also view the full list of commands on our [Azure Developer CLI command](https://aka.ms/azure-dev/ref) page. + +### Security + +#### Roles + +This template creates the [managed identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) for your apps inside your Azure Active Directory tenant, and they are used to authenticate your apps with Azure and other services that support Azure AD authentication like Key Vault via access policies. You will see principalId referenced in the infrastructure as code files, that refers to the id of the currently logged in Azure Developer CLI user, which will be granted access policies and permissions to run the application locally. To view your managed identity in the Azure Portal, follow these [steps](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-view-managed-identity-service-principal-portal). + +### Reporting AZD Issues and Feedback + +If you have any feature requests, issues, or areas for improvement, please [file an issue](https://aka.ms/azure-dev/issues). To keep up-to-date, ask questions, or share suggestions, join our [GitHub Discussions](https://aka.ms/azure-dev/discussions). You may also contact us via AzDevTeam@microsoft.com. + +## Run with Azure CLI + +Azure Spring Apps enables you to easily run a Spring Boot applications on Azure. + +This quickstart shows you how to deploy an existing Java Spring Cloud application to Azure. When you're finished, you can continue to manage the application via the Azure CLI or switch to using the Azure Portal. + +* [Deploy Spring Boot apps using Azure Spring Apps and MySQL](#deploy-spring-boot-apps-using-azure-spring-cloud-and-mysql) + * [What will you experience](#what-will-you-experience) + * [What you will need](#what-you-will-need) + * [Install the Azure CLI extension](#install-the-azure-cli-extension) + * [Clone and build the repo](#clone-and-build-the-repo) + * [Unit 1 - Deploy and monitor Spring Boot apps](#unit-1---deploy-and-monitor-spring-boot-apps) + * [Unit 2 - AUTOMATE deployments using GitHub Actions](#unit-2---automate-deployments-using-github-actions) + * [Unit 3 - Enable Managed Identities for applications in Azure Spring Apps](#unit-3---enable-managed-identities-for-applications-in-azure-spring-apps) + * [Next Steps](#next-steps) + +### What will you experience +You will: +- Build existing Spring Boot applications +- Provision an Azure Spring Apps service instance. If you prefer Terraform, you may also provision using Terraform, see [`README-terraform`](./terraform/README-terraform.md) +- Deploy applications to Azure +- Connect applications to Azure Database for MySQL using Azure AD authentication +- Open the application +- Monitor applications +- Automate deployments using GitHub Actions +- Manage application secrets using Azure KeyVault + +### What you will need + +In order to deploy a Java app to cloud, you need an Azure subscription. If you do not already have an Azure subscription, you can activate your [MSDN subscriber benefits](https://azure.microsoft.com/pricing/member-offers/msdn-benefits-details/) or sign up for a [free Azure account]((https://azure.microsoft.com/free/)). + +In addition, you will need the following: + +| [Azure CLI version 2.44.0 or higher](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) +| [Java 17](https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-17) +| [Maven](https://maven.apache.org/download.cgi) +| [Git](https://git-scm.com/) +| + +> Note - The Bash shell. While Azure CLI should behave identically on all environments, shell +semantics vary. Therefore, only bash can be used with the commands in this repo. +To complete these repo steps on Windows, use Git Bash that accompanies the Windows distribution of + +#### OR Use Azure Cloud Shell + +Or, you can use the Azure Cloud Shell. Azure hosts Azure Cloud Shell, an interactive shell +environment that you can use through your browser. You can use the Bash with Cloud Shell +to work with Azure services. You can use the Cloud Shell pre-installed commands to run the +code in this README without having to install anything on your local environment. To start Azure +Cloud Shell: go to [https://shell.azure.com](https://shell.azure.com), or select the +Launch Cloud Shell button to open Cloud Shell in your browser. + +To run the code in this article in Azure Cloud Shell: + +1. Start Cloud Shell. + +1. Select the Copy button on a code block to copy the code. + +1. Paste the code into the Cloud Shell session by selecting Ctrl+Shift+V on Windows and Linux or by selecting Cmd+Shift+V on macOS. + +1. Select Enter to run the code. + +### Install the Azure CLI extension + +Install the Azure Spring extension for the Azure CLI using the following command + +```bash + az extension add --name spring +``` +Note - `spring` CLI extension `1.5.0` or later is a pre-requisite to enable the latest Java in-process agent for Application Insights. If you already have the CLI extension, you may need to upgrade to the latest using -- + +```bash + az extension update --name spring +``` + +### Clone and build the repo + +#### Create a new folder and clone the sample app repository to your Azure Cloud account + +```bash + mkdir source-code + git clone https://github.com/azure-samples/spring-petclinic-microservices +``` + +#### Change directory and build the project + +```bash + cd spring-petclinic-microservices + ./mvnw clean package -DskipTests +``` +This will take a few minutes. + +### Unit-1 - Deploy and monitor Spring Boot apps + +#### Prepare your environment for deployments + +Create a bash script with environment variables by making a copy of the supplied template: + +```bash + cp .scripts/setup-env-variables-azure-template.sh .scripts/setup-env-variables-azure.sh +``` + +Open `.scripts/setup-env-variables-azure.sh` and enter the following information: + +```bash + export SUBSCRIPTION=subscription-id # customize this + export RESOURCE_GROUP=resource-group-name # customize this + ... + export SPRING_CLOUD_SERVICE=azure-spring-cloud-name # customize this + ... + export MYSQL_SERVER_NAME=mysql-servername # customize this + ... + export MYSQL_SERVER_ADMIN_NAME=admin-name # customize this + ... + export MYSQL_SERVER_ADMIN_PASSWORD=SuperS3cr3t # customize this + ... +``` + +Then, set the environment: + +```bash + source .scripts/setup-env-variables-azure.sh +``` + +#### Login to Azure + +Login to the Azure CLI and choose your active subscription. Be sure to choose the active subscription that is whitelisted for Azure Spring Apps + +```bash + az login + az account list -o table + az account set --subscription ${SUBSCRIPTION} +``` + +#### Create Azure Spring App service instance + +Prepare a name for your Azure Spring App service. The name must be between 4 and 32 characters long and can contain only lowercase letters, numbers, and hyphens. The first character of the service name must be a letter and the last character must be either a letter or a number. + +Create a resource group to contain your Azure Spring App service. + +```bash + az group create --name ${RESOURCE_GROUP} \ + --location ${REGION} +``` + +Create an instance of Azure Spring App. + +```bash + az spring create --name ${SPRING_CLOUD_SERVICE} \ + --sku standard \ + --sampling-rate 100 \ + --resource-group ${RESOURCE_GROUP} \ + --location ${REGION} +``` + +The service instance will take around five minutes to deploy. + +Set your default resource group name and cluster name using the following commands: + +```bash + az configure --defaults \ + group=${RESOURCE_GROUP} \ + location=${REGION} \ + spring=${SPRING_CLOUD_SERVICE} +``` + +#### Create and configure Log Analytics Workspace + +Create a Log Analytics Workspace using Azure CLI: + +```bash + az monitor log-analytics workspace create \ + --workspace-name ${LOG_ANALYTICS} \ + --resource-group ${RESOURCE_GROUP} \ + --location ${REGION} + + export LOG_ANALYTICS_RESOURCE_ID=$(az monitor log-analytics workspace show \ + --resource-group ${RESOURCE_GROUP} \ + --workspace-name ${LOG_ANALYTICS} \ + --query 'id' \ + --output tsv) + + export SPRING_CLOUD_RESOURCE_ID=$(az spring show \ + --name ${SPRING_CLOUD_SERVICE} \ + --resource-group ${RESOURCE_GROUP} \ + --query 'id' \ + --output tsv) +``` + +Setup diagnostics and publish logs and metrics from Spring Boot apps to Azure Log Analytics: + +```bash + az monitor diagnostic-settings create --name "send-logs-and-metrics-to-log-analytics" \ + --resource ${SPRING_CLOUD_RESOURCE_ID} \ + --workspace ${LOG_ANALYTICS_RESOURCE_ID} \ + --logs '[ + { + "category": "ApplicationConsole", + "enabled": true, + "retentionPolicy": { + "enabled": false, + "days": 0 + } + }, + { + "category": "SystemLogs", + "enabled": true, + "retentionPolicy": { + "enabled": false, + "days": 0 + } + }, + { + "category": "IngressLogs", + "enabled": true, + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ]' \ + --metrics '[ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ]' +``` + +#### Load Spring Apps Config Server + +Use the `config-file.yml` in the root of this project to load configuration into the Config Server in Azure Spring Apps. + +```bash + az spring config-server set \ + --config-file config-file.yml \ + --name ${SPRING_CLOUD_SERVICE} +``` + +#### Create applications in Azure Spring Apps + +Create 5 apps. + +```bash + az spring app create --name ${API_GATEWAY} --instance-count 1 --assign-endpoint true \ + --memory 2Gi \ + --runtime-version Java_17 \ + --jvm-options='-Xms2048m -Xmx2048m' + + az spring app create --name ${ADMIN_SERVER} --instance-count 1 --assign-endpoint true \ + --memory 2Gi \ + --runtime-version Java_17 \ + --jvm-options='-Xms2048m -Xmx2048m' + + az spring app create --name ${CUSTOMERS_SERVICE} --instance-count 1 \ + --memory 2Gi \ + --runtime-version Java_17 \ + --jvm-options='-Xms2048m -Xmx2048m' + + az spring app create --name ${VETS_SERVICE} --instance-count 1 \ + --memory 2Gi \ + --runtime-version Java_17 \ + --jvm-options='-Xms2048m -Xmx2048m' + + az spring app create --name ${VISITS_SERVICE} --instance-count 1 \ + --memory 2Gi \ + --runtime-version Java_17 \ + --jvm-options='-Xms2048m -Xmx2048m' +``` + +#### Create MySQL Database + +Create a MySQL database in Azure Database for MySQL. + +```bash + # create mysql server and provide access from Azure resources + az mysql flexible-server create \ + --name ${MYSQL_SERVER_NAME} \ + --resource-group ${RESOURCE_GROUP} \ + --location ${REGION} \ + --admin-user ${MYSQL_SERVER_ADMIN_NAME} \ + --admin-password ${MYSQL_SERVER_ADMIN_PASSWORD} \ + --public-access 0.0.0.0 \ + --tier Burstable \ + --sku-name Standard_B1ms \ + --storage-size 32 + + # allow access from your dev machine for testing + MY_IP=$(curl http://whatismyip.akamai.com) + az mysql flexible-server firewall-rule create \ + --resource-group ${RESOURCE_GROUP} \ + --name ${MYSQL_SERVER_NAME} \ + --rule-name devMachine \ + --start-ip-address ${MY_IP} \ + --end-ip-address ${MY_IP} + + # create database + az mysql flexible-server db create \ + --resource-group ${RESOURCE_GROUP} \ + --server-name ${MYSQL_SERVER_NAME} \ + --database-name ${MYSQL_DATABASE_NAME} + + # increase connection timeout + az mysql flexible-server parameter set \ + --resource-group ${RESOURCE_GROUP} \ + --server ${MYSQL_SERVER_NAME} \ + --name wait_timeout \ + --value 2147483 + + # set timezone + az mysql flexible-server parameter set \ + --resource-group ${RESOURCE_GROUP} \ + --server ${MYSQL_SERVER_NAME} \ + --name time_zone \ + --value "US/Pacific" + + # create managed identity for mysql. By assigning the identity to the mysql server, it will enable Azure AD authentication + az identity create \ + --name ${MYSQL_IDENTITY} \ + --resource-group ${RESOURCE_GROUP} \ + --location ${REGION} + + IDENTITY_ID=$(az identity show --name ${MYSQL_IDENTITY} --resource-group ${RESOURCE_GROUP} --query id -o tsv) + + # Customer service connection + az spring connection create mysql-flexible \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --app ${CUSTOMERS_SERVICE} \ + --deployment default \ + --tg ${RESOURCE_GROUP} \ + --server ${MYSQL_SERVER_NAME} \ + --database ${MYSQL_DATABASE_NAME} \ + --system-identity mysql-identity-id=$IDENTITY_ID \ + --client-type springboot \ + --yes + + # Vets service connection + az spring connection create mysql-flexible \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --app ${VETS_SERVICE} \ + --deployment default \ + --tg ${RESOURCE_GROUP} \ + --server ${MYSQL_SERVER_NAME} \ + --database ${MYSQL_DATABASE_NAME} \ + --system-identity mysql-identity-id=$IDENTITY_ID \ + --client-type springboot \ + --yes + + # Visits service connection + az spring connection create mysql-flexible \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --app ${VISITS_SERVICE} \ + --deployment default \ + --tg ${RESOURCE_GROUP} \ + --server ${MYSQL_SERVER_NAME} \ + --database ${MYSQL_DATABASE_NAME} \ + --system-identity mysql-identity-id=$IDENTITY_ID \ + --client-type springboot \ + --yes +``` + +#### Deploy Spring Boot applications and set environment variables + +Deploy Spring Boot applications to Azure. + +```bash +az spring app deploy \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --name ${API_GATEWAY} \ + --artifact-path ${API_GATEWAY_JAR} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless + +az spring app deploy \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --name ${ADMIN_SERVER} \ + --artifact-path ${ADMIN_SERVER_JAR} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless + +az spring app deploy \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --name ${CUSTOMERS_SERVICE} \ + --artifact-path ${CUSTOMERS_SERVICE_JAR} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless + +az spring app deploy \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --name ${VETS_SERVICE} \ + --artifact-path ${VETS_SERVICE_JAR} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless + +az spring app deploy \ + --resource-group ${RESOURCE_GROUP} \ + --service ${SPRING_CLOUD_SERVICE} \ + --name ${VISITS_SERVICE} \ + --artifact-path ${VISITS_SERVICE_JAR} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless +``` + +```bash + az spring app show --name ${API_GATEWAY} --query properties.url --output tsv +``` + +Navigate to the URL provided by the previous command to open the Pet Clinic application. + +![](./media/petclinic.jpg) + +#### Monitor Spring Boot applications + +##### Use the Petclinic application and make a few REST API calls + +Open the Petclinic application and try out a few tasks - view pet owners and their pets, +vets, and schedule pet visits: + +```bash +open https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/ +``` + +You can also `curl` the REST API exposed by the Petclinic application. The admin REST +API allows you to create/update/remove items in Pet Owners, Pets, Vets and Visits. +You can run the following curl commands: + +```bash +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/owners +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/owners/4 +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/petTypes +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/owners/3/pets/4 +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/owners/6/pets/8 +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/vet/vets +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/visit/owners/6/pets/8/visits +curl -X GET https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/visit/owners/6/pets/8/visits +``` + +##### Get the log stream for API Gateway and Customers Service + +Use the following command to get the latest 100 lines of app console logs from Customers Service. + +```bash +az spring app logs -n ${CUSTOMERS_SERVICE} --lines 100 +``` +By adding a `-f` parameter you can get real-time log streaming from the app. Try log streaming for the API Gateway app. +```bash +az spring app logs -n ${API_GATEWAY} -f +``` +You can use `az spring app logs -h` to explore more parameters and log stream functionalities. + +##### Open Actuator endpoints for API Gateway and Customers Service apps + +Spring Boot includes a number of additional features to help you monitor and manage your application when you push it to production ([Spring Boot Actuator: Production-ready Features](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator)). You can choose to manage and monitor your application by using HTTP endpoints or with JMX. Auditing, health, and metrics gathering can also be automatically applied to your application. + +Actuator endpoints let you monitor and interact with your application. By default, Spring Boot application exposes `health` and `info` endpoints to show arbitrary application info and health information. Apps in this project are pre-configured to expose all the Actuator endpoints. + +You can try them out by opening the following app actuator endpoints in a browser: + +```bash +open https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/actuator/ +open https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/actuator/env +open https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/actuator/configprops + +open https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/actuator +open https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/actuator/env +open https://${SPRING_CLOUD_SERVICE}-${API_GATEWAY}.azuremicroservices.io/api/customer/actuator/configprops +``` + +##### Start monitoring Spring Boot apps and dependencies - in Application Insights + +Open the Application Insights created by Azure Spring Apps and start monitoring Spring Boot applications. You can find the Application Insights in the same Resource Group where +you created an Azure Spring Apps service instance. + +Navigate to the `Application Map` blade: +![](./media/distributed-tracking-new-ai-agent.jpg) + +Navigate to the `Performance` blade: +![](./media/petclinic-microservices-performance.jpg) + +Navigate to the `Performance/Dependencies` blade - you can see the performance number for dependencies, +particularly SQL calls: +![](./media/petclinic-microservices-insights-on-dependencies.jpg) + +Click on a SQL call to see the end-to-end transaction in context: +![](./media/petclinic-microservices-end-to-end-transaction-details.jpg) + +Navigate to the `Failures/Exceptions` blade - you can see a collection of exceptions: +![](./media/petclinic-microservices-failures-exceptions.jpg) + +Click on an exception to see the end-to-end transaction and stacktrace in context: +![](./media/end-to-end-transaction-details.jpg) + +Navigate to the `Metrics` blade - you can see metrics contributed by Spring Boot apps, +Spring Cloud modules, and dependencies. +The chart below shows `gateway-requests` (Spring Cloud Gateway), `hikaricp_connections` +(JDBC Connections) and `http_client_requests`. + +![](./media/petclinic-microservices-metrics.jpg) + +Spring Boot registers a lot number of core metrics: JVM, CPU, Tomcat, Logback... +The Spring Boot auto-configuration enables the instrumentation of requests handled by Spring MVC. +All those three REST controllers `OwnerResource`, `PetResource` and `VisitResource` have been instrumented by the `@Timed` Micrometer annotation at class level. + +* `customers-service` application has the following custom metrics enabled: + * @Timed: `petclinic.owner` + * @Timed: `petclinic.pet` +* `visits-service` application has the following custom metrics enabled: + * @Timed: `petclinic.visit` + +You can see these custom metrics in the `Metrics` blade: +![](./media/petclinic-microservices-custom-metrics.jpg) + +You can use the Availability Test feature in Application Insights and monitor +the availability of applications: +![](./media/petclinic-microservices-availability.jpg) + +Navigate to the `Live Metrics` blade - you can see live metrics on screen with low latencies < 1 second: +![](./media/petclinic-microservices-live-metrics.jpg) + +##### Start monitoring Petclinic logs and metrics in Azure Log Analytics + +Open the Log Analytics that you created - you can find the Log Analytics in the same Resource Group where you created an Azure Spring Apps service instance. + +In the Log Analyics page, selects `Logs` blade and run any of the sample queries supplied below for Azure Spring Apps. + +Type and run the following Kusto query to see application logs: + +```sql + AppPlatformLogsforSpring + | where TimeGenerated > ago(24h) + | limit 500 + | sort by TimeGenerated +``` + +Type and run the following Kusto query to see `customers-service` application logs: +```sql + AppPlatformLogsforSpring + | where AppName has "customers" + | limit 500 + | sort by TimeGenerated +``` + +Type and run the following Kusto query to see errors and exceptions thrown by each app: +```sql + AppPlatformLogsforSpring + | where Log contains "error" or Log contains "exception" + | extend FullAppName = strcat(ServiceName, "/", AppName) + | summarize count_per_app = count() by FullAppName, ServiceName, AppName, _ResourceId + | sort by count_per_app desc + | render piechart +``` + +Type and run the following Kusto query to see all in the inbound calls into Azure Spring Apps: +```sql + AppPlatformIngressLogs + | project TimeGenerated, RemoteAddr, Host, Request, Status, BodyBytesSent, RequestTime, ReqId, RequestHeaders + | sort by TimeGenerated +``` + +Type and run the following Kusto query to see all the logs from the managed Spring Cloud +Config Server managed by Azure Spring Apps: +```sql + AppPlatformSystemLogs + | where LogType contains "ConfigServer" + | project TimeGenerated, Level, LogType, ServiceName, Log + | sort by TimeGenerated +``` + +Type and run the following Kusto query to see all the logs from the managed Spring Cloud +Service Registry managed by Azure Spring Apps: +```sql + AppPlatformSystemLogs + | where LogType contains "ServiceRegistry" + | project TimeGenerated, Level, LogType, ServiceName, Log + | sort by TimeGenerated +``` + +### Unit-2 - Automate deployments using GitHub Actions +```bash +AZURE_CLIENT_ID=$(az ad app create --display-name github-petclinic-actions --query appId --output tsv) +GITHUB_OBJECTID=$(az ad app show --id $AZURE_CLIENT_ID --query id --output tsv) +``` + +* AZURE_TENANT_ID: The tenant ID of the Azure subscription hosting the Azure Spring Apps instance. +* AZURE_SUBSCRIPTION_ID: The subscription ID of the Azure subscription hosting the Azure Spring Apps instance. +* AZURE_CLIENT_ID: The client ID of the Azure Active Directory application created in the previous step. +* RESOURCE_GROUP: The resource group hosting the Azure Spring Apps instance. +* SPRING_APPS_SERVICE_NAME: The name of the Azure Spring Apps instance. + +As you can see, there are not real confidential values, as even if someone gets access to them, they can't do anything with them. They can be replaced by environment variables. + +You will see GitHub Actions triggered to build and deploy all the apps in the repo to your Azure Spring Apps instance. +![](./media/automate-deployments-using-github-actions.png) + +### Unit-3 - Enable Managed Identities for applications in Azure Spring Apps + +Enable System Assigned Identities for applications and export identities to environment. + +```bash + az spring app identity assign --name ${CUSTOMERS_SERVICE} + export CUSTOMERS_SERVICE_IDENTITY=$(az spring app show --name ${CUSTOMERS_SERVICE} --query 'identity.principalId' --output tsv) + + az spring app identity assign --name ${VETS_SERVICE} + export VETS_SERVICE_IDENTITY=$(az spring app show --name ${VETS_SERVICE} --query 'identity.principalId' --output tsv) + + az spring app identity assign --name ${VISITS_SERVICE} + export VISITS_SERVICE_IDENTITY=$(az spring app show --name ${VISITS_SERVICE} --query 'identity.principalId' --output tsv) +``` + +#### Activate applications to use passwordless access to MySql + +Configuration repo contains a profile for passwordless access to MySql. To activate it, we need to add the following environment variable `SPRING_PROFILES_ACTIVE=passwordless` + +```bash + az spring app update --name ${CUSTOMERS_SERVICE} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless + + az spring app update --name ${VETS_SERVICE} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless + + az spring app update --name ${VISITS_SERVICE} \ + --jvm-options='-Xms2048m -Xmx2048m' \ + --env SPRING_PROFILES_ACTIVE=passwordless +``` + +## More about ASA +In this quickstart, you've deployed an existing Spring Boot-based app using AZD. To learn more about Azure Spring Apps, go to: + +- [Azure Spring Apps](https://azure.microsoft.com/products/spring-apps/) +- [Azure Spring Apps docs](https://learn.microsoft.com/azure/developer/java/) +- [Deploy Spring microservices from scratch](https://github.com/microsoft/azure-spring-cloud-training) +- [Deploy existing Spring microservices](https://github.com/Azure-Samples/azure-spring-cloud) +- [Azure for Java Cloud Developers](https://learn.microsoft.com/azure/developer/java/) +- [Spring Cloud Azure](https://spring.io/projects/spring-cloud-azure) +- [Spring Cloud](https://spring.io/projects/spring-cloud) + +## Credits + +This Spring microservices sample is forked from +[spring-petclinic/spring-petclinic-microservices](https://github.com/spring-petclinic/spring-petclinic-microservices) - see [Petclinic README](./README-petclinic.md). + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/docker/Dockerfile b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/docker/Dockerfile new file mode 100644 index 00000000..d49a4b3f --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/docker/Dockerfile @@ -0,0 +1,27 @@ +FROM mcr.microsoft.com/openjdk/jdk:17-mariner as builder +WORKDIR application +ARG ARTIFACT_NAME +COPY ${ARTIFACT_NAME}.jar application.jar +RUN java -Djarmode=layertools -jar application.jar extract + +FROM mcr.microsoft.com/openjdk/jdk:17-mariner + +WORKDIR application + +ARG EXPOSED_PORT +EXPOSE ${EXPOSED_PORT} + +# ENV SPRING_PROFILES_ACTIVE docker + +COPY --from=builder application/dependencies/ ./ +# fix for https://stackoverflow.com/questions/51115856/docker-failed-to-export-image-failed-to-create-image-failed-to-get-layer +# (only last copy caused issue) +# this seems to be triggered by using btrfs: +# https://github.com/moby/moby/issues/36573 +RUN true +COPY --from=builder application/spring-boot-loader/ ./ +RUN true +COPY --from=builder application/snapshot-dependencies/ ./ +RUN true +COPY --from=builder application/application/ ./ +ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw new file mode 100644 index 00000000..35ff643b --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw.cmd b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw.cmd new file mode 100644 index 00000000..dae46d49 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/pom.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/pom.xml new file mode 100644 index 00000000..9902ce03 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/pom.xml @@ -0,0 +1,299 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.0.1 + + + org.springframework.samples + spring-petclinic-microservices + 3.0.1 + ${project.artifactId} + pom + + + 17 + 3.17.1 + + 2022.0.0 + 2.3.10 + 1.7.1 + + springcommunity + 9090 + ${basedir}/docker + v0.6.1 + 1.2.0 + 5.0.0 + 4.0.5 + + + + + + io.pivotal.spring.cloud + spring-cloud-services-dependencies + ${spring-cloud-services.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + de.codecentric + chaos-monkey-spring-boot + ${chaos-monkey-spring-boot.version} + + + org.jolokia + jolokia-core + ${jolokia-core.version} + + + org.assertj + assertj-core + ${assertj.version} + test + + + com.azure.spring + spring-cloud-azure-dependencies + ${version.spring.cloud.azure} + pom + import + + + + + + + default + + true + + + spring-petclinic-customers-service + spring-petclinic-vets-service + spring-petclinic-visits-service + spring-petclinic-api-gateway + + + + + release + + spring-petclinic-customers-service + spring-petclinic-vets-service + spring-petclinic-visits-service + spring-petclinic-api-gateway + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-release-plugin + 3.0.1 + + v@{project.version} + + + + + + + + spring-apps + + spring-petclinic-customers-service + spring-petclinic-vets-service + spring-petclinic-visits-service + spring-petclinic-api-gateway + + + + + spring-apps-enterprise + + spring-petclinic-customers-service + spring-petclinic-vets-service + spring-petclinic-visits-service + + + + springboot + + + + src/main/resources/application.yml + + + + spring-petclinic-customers-service + spring-petclinic-vets-service + spring-petclinic-visits-service + spring-petclinic-api-gateway + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + build-info + repackage + + + + ${project.build.sourceEncoding} + ${project.reporting.outputEncoding} + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + + copy-jar + package + + copy-resources + + + ${project.build.directory}/docker + + + ${project.build.directory} + + *.jar + + + + ${docker.image.dockerfile.dir} + + Dockerfile + + + + + + + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + + revision + + + + + true + yyyy-MM-dd'T'HH:mm:ssZ + true + ${project.build.outputDirectory}/git.properties + false + false + + + + + + + + buildDocker + + spring-petclinic-customers-service + spring-petclinic-vets-service + spring-petclinic-visits-service + spring-petclinic-api-gateway + + + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + install + + build + + + + + ${docker.image.prefix}/${project.artifactId} + ${docker.image.dockerfile.dir} + docker-hub + https://index.docker.io/v1/ + + + / + ${project.build.directory} + ${project.build.finalName}.jar + + + + ${project.build.finalName} + ${docker.image.exposed.port} + ${docker.image.dockerize.version} + + + + + + + + + + dev + + + env + development + + + + spring-petclinic-customers-service + spring-petclinic-vets-service + spring-petclinic-visits-service + spring-petclinic-api-gateway + + + + diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/.gitignore b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/.gitignore new file mode 100644 index 00000000..c71ea97a --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/.gitignore @@ -0,0 +1 @@ +/.apt_generated/ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/manifest.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/manifest.yml new file mode 100644 index 00000000..bafb4fac --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/manifest.yml @@ -0,0 +1,24 @@ +--- +applications: +- name: spring-petclinic-api-gateway + random-route: true + memory: 1G + path: target/api-gateway-3.0.1.jar + health-check-type: port + health-check-invocation-timeout: 30 + routes: + - route: spring-petclinic-api-gateway.apps.xiading-tanzu-tas.azdmss-test.net + buildpacks: + - java_buildpack_offline + env: + JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 17.+ }}' + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}' + SPRING_PROFILES_ACTIVE: default + SERVER_PORT: 8080 + EUREKA_INSTANCE_SECURE_PORT_ENABLED: false + EUREKA_INSTANCE_NON_SECURE_PORT: 8080 + EUREKA_INSTANCE_PREFER_IP_ADDRESS: true + services: + - service-registry + - config-server + \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/pom.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/pom.xml new file mode 100644 index 00000000..fac21944 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/pom.xml @@ -0,0 +1,185 @@ + + + 4.0.0 + + org.springframework.samples.petclinic.api + api-gateway + jar + Spring PetClinic API Gateway + + + org.springframework.samples + spring-petclinic-microservices + 3.0.1 + + + + 3.3.7-1 + 3.6.0 + 1.8.2 + 1.0.20 + 1.8.0 + 8081 + ${basedir}/../docker + + + + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-reactor-resilience4j + + + io.pivotal.spring.cloud + spring-cloud-services-starter-config-client + + + io.pivotal.spring.cloud + spring-cloud-services-starter-service-registry + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + org.jolokia + jolokia-core + + + org.projectlombok + lombok + provided + + + io.micrometer + micrometer-registry-prometheus + + + io.github.resilience4j + resilience4j-micrometer + + + + + org.webjars + angularjs + ${webjars-angular.version} + + + org.webjars + jquery + ${webjars-jquery.version} + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + org.webjars + angular-ui-router + ${webjars-angular-ui-router.version} + + + org.webjars + webjars-locator-core + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + com.squareup.okhttp3 + mockwebserver + test + + + + + + + ro.isdc.wro4j + wro4j-maven-plugin + ${wro4j.version} + + + generate-resources + + run + + + + + + ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory + ${project.build.directory}/classes/static/css + ${basedir}/src/main/wro/wro.xml + ${basedir}/src/main/wro/wro.properties + ${basedir}/src/main/less + + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + + + org.mockito + mockito-core + ${mockito.version} + + + + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + diff --git a/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 b/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 new file mode 100644 index 00000000..1c88d4be --- /dev/null +++ b/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 @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api; + +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder; +import org.springframework.cloud.client.circuitbreaker.Customizer; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.server.RequestPredicates; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; + +import java.time.Duration; + + +/** + * @author Maciej Szarlinski + */ +@EnableDiscoveryClient +@SpringBootApplication +public class ApiGatewayApplication { + + public static void main(String[] args) { + SpringApplication.run(ApiGatewayApplication.class, args); + } + + @Bean + @LoadBalanced + RestTemplate loadBalancedRestTemplate() { + return new RestTemplate(); + } + + @Bean + @LoadBalanced + public WebClient.Builder loadBalancedWebClientBuilder() { + return WebClient.builder(); + } + + @Value("classpath:/static/index.html") + private Resource indexHtml; + + /** + * workaround solution for forwarding to index.html + * @see #9785 + */ + @Bean + @Profile("!development") + RouterFunction routerFunction() { + RouterFunction router = RouterFunctions.resources("/**", new ClassPathResource("static/")) + .andRoute(RequestPredicates.GET("/"), + request -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(indexHtml)); + return router; + } + + /** + * Default Resilience4j circuit breaker configuration + */ + @Bean + public Customizer defaultCustomizer() { + return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) + .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()) + .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(4)).build()) + .build()); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..b5f65559 --- /dev/null +++ b/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 @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.api.dto.OwnerDetails; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +/** + * @author Maciej Szarlinski + */ +@Component +@RequiredArgsConstructor +@Profile("!development") +public class CustomersServiceClient { + + private final WebClient.Builder webClientBuilder; + + public Mono getOwner(final int ownerId) { + return webClientBuilder.build().get() + .uri("http://customers-service/owners/{ownerId}", ownerId) + .retrieve() + .bodyToMono(OwnerDetails.class); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..93d4e1b4 --- /dev/null +++ b/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 @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.api.dto.Visits; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.List; + +import static java.util.stream.Collectors.joining; + +/** + * @author Maciej Szarlinski + */ +@Component +@RequiredArgsConstructor +@Profile("!development") +public class VisitsServiceClient { + + // Could be changed for testing purpose + private String hostname = "http://visits-service/"; + + private final WebClient.Builder webClientBuilder; + + public Mono getVisitsForPets(final List petIds) { + return webClientBuilder.build() + .get() + .uri(hostname + "pets/visits?petId={petId}", joinIds(petIds)) + .retrieve() + .bodyToMono(Visits.class); + } + + private String joinIds(List petIds) { + return petIds.stream().map(Object::toString).collect(joining(",")); + } + + void setHostname(String hostname) { + this.hostname = hostname; + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..9f1b2fee --- /dev/null +++ b/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 @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.boundary.web; + +import lombok.RequiredArgsConstructor; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.samples.petclinic.api.application.CustomersServiceClient; +import org.springframework.samples.petclinic.api.application.VisitsServiceClient; +import org.springframework.samples.petclinic.api.dto.OwnerDetails; +import org.springframework.samples.petclinic.api.dto.Visits; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author Maciej Szarlinski + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/gateway") +@Profile("!development") +public class ApiGatewayController { + + private final CustomersServiceClient customersServiceClient; + + private final VisitsServiceClient visitsServiceClient; + + private final ReactiveCircuitBreakerFactory cbFactory; + + @GetMapping(value = "owners/{ownerId}") + public Mono getOwnerDetails(final @PathVariable int ownerId) { + return customersServiceClient.getOwner(ownerId) + .flatMap(owner -> + visitsServiceClient.getVisitsForPets(owner.getPetIds()) + .transform(it -> { + ReactiveCircuitBreaker cb = cbFactory.create("getOwnerDetails"); + return cb.run(it, throwable -> emptyVisitsForPets()); + }) + .map(addVisitsToOwner(owner)) + ); + + } + + private Function addVisitsToOwner(OwnerDetails owner) { + return visits -> { + owner.getPets() + .forEach(pet -> pet.getVisits() + .addAll(visits.getItems().stream() + .filter(v -> v.getPetId() == pet.getId()) + .collect(Collectors.toList())) + ); + return owner; + }; + } + + private Mono emptyVisitsForPets() { + return Mono.just(new Visits()); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..35391c02 --- /dev/null +++ b/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 @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * @author Maciej Szarlinski + */ +@Data +public class OwnerDetails { + + private int id; + + private String firstName; + + private String lastName; + + private String address; + + private String city; + + private String telephone; + + private final List pets = new ArrayList<>(); + + @JsonIgnore + public List getPetIds() { + return pets.stream() + .map(PetDetails::getId) + .collect(toList()); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..bf064fe6 --- /dev/null +++ b/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 @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Maciej Szarlinski + */ +@Data +public class PetDetails { + + private int id; + + private String name; + + private String birthDate; + + private PetType type; + + private final List visits = new ArrayList<>(); + +} diff --git a/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 b/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 new file mode 100644 index 00000000..e6b6d9e5 --- /dev/null +++ b/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 @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.dto; + +import lombok.Data; + +/** + * @author Maciej Szarlinski + */ +@Data +public class PetType { + + private String name; +} diff --git a/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 b/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 new file mode 100644 index 00000000..e9f35354 --- /dev/null +++ b/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 @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Maciej Szarlinski + */ +@Data +@NoArgsConstructor +public class VisitDetails { + + private Integer id = null; + + private Integer petId = null; + + private String date = null; + + private String description = null; +} diff --git a/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 b/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 new file mode 100644 index 00000000..fb5851f1 --- /dev/null +++ b/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 @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.dto; + +import java.util.ArrayList; +import java.util.List; + +import lombok.NoArgsConstructor; +import lombok.Value; + +/** + * @author Maciej Szarlinski + */ +@Value +public class Visits { + + private List items = new ArrayList<>(); + +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/header.less b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/header.less new file mode 100644 index 00000000..7cb1a788 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/header.less @@ -0,0 +1,73 @@ +.navbar { + border-top: 4px solid #6db33f; + background-color: #34302d; + margin-bottom: 0px; + border-bottom: 0; + border-left: 0; + border-right: 0; +} + +.navbar a.navbar-brand { + background: url("../images/spring-logo-dataflow.png") -1px -1px no-repeat; + margin: 12px 0 6px; + width: 229px; + height: 46px; + display: inline-block; + text-decoration: none; + padding: 0; +} + +.navbar a.navbar-brand span { + display: block; + width: 229px; + height: 46px; + background: url("../images/spring-logo-dataflow.png") -1px -48px no-repeat; + opacity: 0; + -moz-transition: opacity 0.12s ease-in-out; + -webkit-transition: opacity 0.12s ease-in-out; + -o-transition: opacity 0.12s ease-in-out; +} + +.navbar a:hover.navbar-brand span { + opacity: 1; +} + +.navbar li > a, .navbar-text { + font-family: "montserratregular", sans-serif; + text-shadow: none; + font-size: 14px; + +/* line-height: 14px; */ + padding: 28px 20px; + transition: all 0.15s; + -webkit-transition: all 0.15s; + -moz-transition: all 0.15s; + -o-transition: all 0.15s; + -ms-transition: all 0.15s; +} + +.navbar li > a { + text-transform: uppercase; +} + +.navbar .navbar-text { + margin-top: 0; + margin-bottom: 0; +} +.navbar li:hover > a { + color: #eeeeee; + background-color: #6db33f; +} + +.navbar-toggle { + border-width: 0; + + .icon-bar + .icon-bar { + margin-top: 3px; + } + .icon-bar { + width: 19px; + height: 3px; + } + +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/petclinic.less b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/petclinic.less new file mode 100644 index 00000000..26ff4ca3 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/petclinic.less @@ -0,0 +1,250 @@ +/* + * Copyright 2016 the original author or authors. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@icon-font-path: "../webjars/bootstrap/fonts/"; + +@spring-green: #6db33f; +@spring-dark-green: #5fa134; +@spring-brown: #34302D; +@spring-grey: #838789; +@spring-light-grey: #f1f1f1; + +@body-bg: @spring-light-grey; +@text-color: @spring-brown; +@link-color: @spring-dark-green; +@link-hover-color: @spring-dark-green; + +@navbar-default-link-color: @spring-light-grey; +@navbar-default-link-active-color: @spring-light-grey; +@navbar-default-link-hover-color: @spring-light-grey; +@navbar-default-link-hover-bg: @spring-green; +@navbar-default-toggle-icon-bar-bg: @spring-light-grey; +@navbar-default-toggle-hover-bg: transparent; +@navbar-default-link-active-bg: @spring-green; + +@border-radius-base: 0; +@border-radius-large: 0; +@border-radius-small: 0; + +@btn-default-color: @spring-light-grey; +@btn-default-bg: @spring-brown; +@btn-default-border: @spring-green; + +@nav-tabs-active-link-hover-color: @spring-light-grey; +@nav-tabs-active-link-hover-bg: @spring-brown; +@nav-tabs-active-link-hover-border-color: @spring-brown; +@nav-tabs-border-color: @spring-brown; + +@pagination-active-bg: @spring-brown; +@pagination-active-border: @spring-green; +@table-border-color: @spring-brown; + +@import "typography.less"; +@import "header.less"; + +.table > thead > tr > th { + background-color: lighten(@spring-brown, 3%); + color: @spring-light-grey; +} + +.table-filter { + background-color: @spring-brown; + padding: 9px 12px; +} + +.nav > li > a { + color: @spring-grey; +} + +.btn-default { + border-width: 2px; + transition: border 0.15s; + -webkit-transition: border 0.15s; + -moz-transition: border 0.15s; + -o-transition: border 0.15s; + -ms-transition: border 0.15s; + + &:hover, + &:focus, + &:active, + &.active, + .open .dropdown-toggle& { + background-color: @spring-brown; + border-color: @spring-brown; + } +} + + +.container .text-muted { + margin: 20px 0; +} + +code { + font-size: 80%; +} + +.xd-container { + margin-top: 40px; + margin-bottom: 100px; + padding-left: 5px; + padding-right: 5px; +} + +h1 { + margin-bottom: 15px +} + +.index-page--subtitle { + font-size: 16px; + line-height: 24px; + margin: 0 0 30px; +} + +.form-horizontal button.btn-inverse { + margin-left: 32px; +} + +#job-params-modal .modal-dialog { + width: 90%; + margin-left:auto; + margin-right:auto; +} + +[ng-cloak].splash { + display: block !important; +} +[ng-cloak] { + display: none; +} + +.splash { + background: @spring-green; + color: @spring-brown; + display: none; +} + +.error-page { + margin-top: 100px; + text-align: center; +} + +.error-page .error-title { + font-size: 24px; + line-height: 24px; + margin: 30px 0 0; +} + +table td { + vertical-align: middle; +} + +table td .progress { + margin-bottom: 0; +} + +table td.action-column { + width: 1px; +} + +.help-block { + color: lighten(@text-color, 50%); // lighten the text some for contrast +} + +.xd-containers { + font-size: 15px; +} + +.cluster-view > table td { + vertical-align: top; +} + +.cluster-view .label, .cluster-view .column-block { + display: block; +} + +.cluster-view .input-group-addon { + width: 0%; +} + +.cluster-view { + margin-bottom: 0; +} + +.deployment-status-deployed { + .label-success; +} + +.deployment-status-incomplete { + .label-warning; +} + +.deployment-status-failed { + .label-danger; +} + +.deployment-status-deploying { + .label-info +} +.deployment-status-na { +} + +.container-details-table th { + background-color: lighten(@spring-brown, 3%); + color: @spring-light-grey; +} + +.status-help-content-table td { + color: @spring-brown; +} + +.alert-success { + .alert-variant(fade(@alert-success-bg, 70%); @alert-success-border; @alert-success-text); +} +.alert-info { + .alert-variant(fade(@alert-info-bg, 70%); @alert-info-border; @alert-info-text); +} +.alert-warning { + .alert-variant(fade(@alert-warning-bg, 70%); @alert-warning-border; @alert-warning-text); +} +.alert-danger { + .alert-variant(fade(@alert-danger-bg, 70%); @alert-danger-border; @alert-danger-text); +} + +.myspinner { + animation-name: spinner; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: linear; + + -webkit-transform-origin: 49% 50%; + -webkit-animation-name: spinner; + -webkit-animation-duration: 2s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: linear; +} + +@keyframes "spinner" { + from { transform: rotate( 0deg ); } + to { transform: rotate( 360deg ); } +} + +@-webkit-keyframes "spinner" { + from { -webkit-transform: rotate( 0deg ); } + to { -webkit-transform: rotate( 360deg ); } +} + +hr { + border-top: 1px dotted @spring-brown; +} + +@import "responsive.less"; diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/responsive.less b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/responsive.less new file mode 100644 index 00000000..8f3b2154 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/responsive.less @@ -0,0 +1,41 @@ +@media (max-width: 768px) { + .navbar-toggle { + position:absolute; + z-index: 9999; + left:0px; + top:0px; + } + + .navbar a.navbar-brand { + display: block; + margin: 0 auto 0 auto; + width: 148px; + height: 50px; + float: none; + background: url("../images/spring-logo-dataflow-mobile.png") 0 center no-repeat; + } + + .homepage-billboard .homepage-subtitle { + font-size: 21px; + line-height: 21px; + } + + .navbar a.navbar-brand span { + display: none; + } + + .navbar { + border-top-width: 0; + } + + .xd-container { + margin-top: 20px; + margin-bottom: 30px; + } + + .index-page--subtitle { + margin-top: 10px; + margin-bottom: 30px; + } + +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/typography.less b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/typography.less new file mode 100644 index 00000000..8b8436e1 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/less/typography.less @@ -0,0 +1,60 @@ +@font-face { + font-family: 'varela_roundregular'; + + src: url('../fonts/varela_round-webfont.eot'); + src: url('../fonts/varela_round-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/varela_round-webfont.woff') format('woff'), + url('../fonts/varela_round-webfont.ttf') format('truetype'), + url('../fonts/varela_round-webfont.svg#varela_roundregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'montserratregular'; + src: url('../fonts/montserrat-webfont.eot'); + src: url('../fonts/montserrat-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/montserrat-webfont.woff') format('woff'), + url('../fonts/montserrat-webfont.ttf') format('truetype'), + url('../fonts/montserrat-webfont.svg#montserratregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +body, h1, h2, h3, p, input { + margin: 0; + font-weight: 400; + font-family: "varela_roundregular", sans-serif; + color: #34302d; +} + +h1 { + font-size: 24px; + line-height: 30px; + font-family: "montserratregular", sans-serif; +} + +h2 { + font-size: 18px; + font-weight: 700; + line-height: 24px; + margin-bottom: 10px; + font-family: "montserratregular", sans-serif; +} + +h3 { + font-size: 16px; + line-height: 24px; + margin-bottom: 10px; + font-weight: 700; +} + +p { + //font-size: 15px; + //line-height: 24px; +} + +strong { + font-weight: 700; + font-family: "montserratregular", sans-serif; +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/application.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/application.yml new file mode 100644 index 00000000..22a55cbe --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/application.yml @@ -0,0 +1,70 @@ +spring: + application: + name: api-gateway + config: + import: optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888/} + cloud: + gateway: + routes: + - id: vets-service + uri: lb://vets-service + predicates: + - Path=/api/vet/** + filters: + - StripPrefix=2 + - id: visits-service + uri: lb://visits-service + predicates: + - Path=/api/visit/** + filters: + - StripPrefix=2 + - id: customers-service + uri: lb://customers-service + predicates: + - Path=/api/customer/** + filters: + - StripPrefix=2 +--- +spring: + config: + activate: + on-profile: development + cloud: + gateway: + routes: + - id: vets-service + uri: lb://vets-service + predicates: + - Path=/api/vet/** + filters: + - StripPrefix=2 + - id: visits-service + uri: lb://visits-service + predicates: + - Path=/api/visit/** + filters: + - StripPrefix=2 + - id: customers-service + uri: lb://customers-service + predicates: + - Path=/api/customer/** + filters: + - StripPrefix=2 + - id: frontend + uri: http://localhost:3000 + predicates: + - Path=/** +--- +server: + port: 8080 +spring: + config: + activate: + on-profile: default + +--- +spring: + config: + activate: + on-profile: docker + import: configserver:http://config-server:8888 diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/logback-spring.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..5d03f794 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/logback-spring.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages.properties b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages.properties new file mode 100644 index 00000000..f916c7a4 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages.properties @@ -0,0 +1,7 @@ +required=is required +notFound=has not been found +duplicate=is already in use +nonNumeric=must be all numeric +duplicateFormSubmission=Duplicate form submission is not allowed +typeMismatch.date=invalid date +typeMismatch.birthDate=invalid date diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_de.properties b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_de.properties new file mode 100644 index 00000000..46c70b5b --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_de.properties @@ -0,0 +1,7 @@ +required=muss angegeben werden +notFound=wurde nicht gefunden +duplicate=ist bereits vergeben +nonNumeric=darf nur numerisch sein +duplicateFormSubmission=Wiederholtes Absenden des Formulars ist nicht erlaubt +typeMismatch.date=ung�ltiges Datum +typeMismatch.birthDate=ung�ltiges Datum diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_en.properties b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_en.properties new file mode 100644 index 00000000..05d519bb --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/messages/messages_en.properties @@ -0,0 +1 @@ +# This file is intentionally empty. Message look-ups will fall back to the default "messages.properties" file. \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.eot b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.eot new file mode 100644 index 00000000..0caea916 Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.eot differ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.svg b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.svg new file mode 100644 index 00000000..7bd96bdf --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.svg @@ -0,0 +1,1283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.ttf b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.ttf new file mode 100644 index 00000000..9953fe62 Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.ttf differ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.woff b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.woff new file mode 100644 index 00000000..eb49333f Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/fonts/montserrat-webfont.woff differ diff --git a/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 b/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 new file mode 100644 index 00000000..dfee0c26 Binary files /dev/null and b/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 differ diff --git a/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 b/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 new file mode 100644 index 00000000..3280e2c4 --- /dev/null +++ b/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 @@ -0,0 +1,7875 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/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 b/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 new file mode 100644 index 00000000..3ca06669 Binary files /dev/null and b/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 differ diff --git a/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 b/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 new file mode 100644 index 00000000..77ba1661 Binary files /dev/null and b/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 differ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/favicon.png b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/favicon.png new file mode 100644 index 00000000..1c649a3c Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/favicon.png differ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/pets.png b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/pets.png new file mode 100644 index 00000000..bb5cf3a3 Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/pets.png differ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/platform-bg.png b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/platform-bg.png new file mode 100644 index 00000000..51218583 Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/images/platform-bg.png differ diff --git a/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 b/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 new file mode 100644 index 00000000..45d24a6f Binary files /dev/null and b/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 differ diff --git a/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 b/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 new file mode 100644 index 00000000..ff7cdbb4 Binary files /dev/null and b/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 differ diff --git a/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 b/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 new file mode 100644 index 00000000..1840af27 Binary files /dev/null and b/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 differ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/index.html b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/index.html new file mode 100644 index 00000000..c947c3e3 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/index.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + PetClinic :: a Spring Framework demonstration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + + diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/app.js b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/app.js new file mode 100644 index 00000000..66db62a2 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/app.js @@ -0,0 +1,36 @@ +'use strict'; +/* App Module */ +var petClinicApp = angular.module('petClinicApp', [ + 'ui.router', 'infrastructure', 'layoutNav', 'layoutFooter', 'layoutWelcome', + 'ownerList', 'ownerDetails', 'ownerForm', 'petForm', 'visits', 'vetList']); + +petClinicApp.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', '$httpProvider', function( + $stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) { + + // safari turns to be lazy sending the Cache-Control header + $httpProvider.defaults.headers.common["Cache-Control"] = 'no-cache'; + $httpProvider.interceptors.push('HttpErrorHandlingInterceptor'); + + $locationProvider.hashPrefix('!'); + + $urlRouterProvider.otherwise('/welcome'); + $stateProvider + .state('app', { + abstract: true, + url: '', + template: '' + }) + .state('welcome', { + parent: 'app', + url: '/welcome', + template: '' + }); +}]); + +['welcome', 'nav', 'footer'].forEach(function(c) { + var mod = 'layout' + c.toUpperCase().substring(0, 1) + c.substring(1); + angular.module(mod, []); + angular.module(mod).component(mod, { + templateUrl: "scripts/fragments/" + c + ".html" + }); +}); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/footer.html b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/footer.html new file mode 100644 index 00000000..cc1e7c51 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/footer.html @@ -0,0 +1,6 @@ +
+
+
Sponsored by Pivotal
+
+
\ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/nav.html b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/nav.html new file mode 100644 index 00000000..6005cfe3 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/nav.html @@ -0,0 +1,38 @@ + diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/welcome.html b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/welcome.html new file mode 100644 index 00000000..df0b55e9 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/fragments/welcome.html @@ -0,0 +1,7 @@ +

Welcome to Petclinic

+ +
+
+ pets logo +
+
\ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/httpErrorHandlingInterceptor.js b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/httpErrorHandlingInterceptor.js new file mode 100644 index 00000000..f65ab687 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/httpErrorHandlingInterceptor.js @@ -0,0 +1,17 @@ +'use strict'; + +/** + * Global HTTP errors handler. + */ +angular.module('infrastructure') + .factory('HttpErrorHandlingInterceptor', function () { + return { + responseError: function (response) { + var error = response.data; + alert(error.error + "\r\n" + error.errors.map(function (e) { + return e.field + ": " + e.defaultMessage; + }).join("\r\n")); + return response; + } + } + }); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/infrastructure.js b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/infrastructure.js new file mode 100644 index 00000000..58dd767c --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/infrastructure/infrastructure.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('infrastructure', []); diff --git a/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 b/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 new file mode 100644 index 00000000..98a43fcb --- /dev/null +++ b/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 @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('ownerDetails') + .component('ownerDetails', { + templateUrl: 'scripts/owner-details/owner-details.template.html', + controller: 'OwnerDetailsController' + }); diff --git a/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 b/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 new file mode 100644 index 00000000..d164378d --- /dev/null +++ b/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 @@ -0,0 +1,10 @@ +'use strict'; + +angular.module('ownerDetails') + .controller('OwnerDetailsController', ['$http', '$stateParams', function ($http, $stateParams) { + var self = this; + + $http.get('api/gateway/owners/' + $stateParams.ownerId).then(function (resp) { + self.owner = resp.data; + }); + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..670f24be --- /dev/null +++ b/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 @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('ownerDetails', ['ui.router']) + .config(['$stateProvider', function ($stateProvider) { + $stateProvider + .state('ownerDetails', { + parent: 'app', + url: '/owners/details/:ownerId', + template: '' + }) + }]); \ No newline at end of file diff --git a/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 b/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 new file mode 100644 index 00000000..ed2810f9 --- /dev/null +++ b/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 @@ -0,0 +1,70 @@ +

Owner Information

+ + + + + + + + + + + + + + + + + + + + + + +
Name{{$ctrl.owner.firstName}} {{$ctrl.owner.lastName}}
Address{{$ctrl.owner.address}}
City{{$ctrl.owner.city}}
Telephone{{$ctrl.owner.telephone}}
+ Edit Owner + + Add New Pet +
+ +

Pets and Visits

+ + + + + + +
+
+
Name
+
{{pet.name}}
+
Birth Date
+
{{pet.birthDate | date:'yyyy MMM dd'}}
+
Type
+
{{pet.type.name}}
+
+
+ + + + + + + + + + + + + + + +
Visit DateDescription
{{visit.date | date:'yyyy MMM dd'}}{{visit.description}}
+ Edit Pet + + Add Visit +
+
+ + + diff --git a/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 b/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 new file mode 100644 index 00000000..e8dec69c --- /dev/null +++ b/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 @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('ownerForm') + .component('ownerForm', { + templateUrl: 'scripts/owner-form/owner-form.template.html', + controller: 'OwnerFormController' + }); diff --git a/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 b/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 new file mode 100644 index 00000000..d25f7a56 --- /dev/null +++ b/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 @@ -0,0 +1,30 @@ +'use strict'; + +angular.module('ownerForm') + .controller('OwnerFormController', ["$http", '$state', '$stateParams', function ($http, $state, $stateParams) { + var self = this; + + var ownerId = $stateParams.ownerId || 0; + + if (!ownerId) { + self.owner = {}; + } else { + $http.get("api/customer/owners/" + ownerId).then(function (resp) { + self.owner = resp.data; + }); + } + + self.submitOwnerForm = function () { + var id = self.owner.id; + + if (id) { + $http.put('api/customer/owners/' + id, self.owner).then(function () { + $state.go('ownerDetails', {ownerId: ownerId}); + }); + } else { + $http.post('api/customer/owners', self.owner).then(function () { + $state.go('owners'); + }); + } + }; + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..1bb61591 --- /dev/null +++ b/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 @@ -0,0 +1,16 @@ +'use strict'; + +angular.module('ownerForm', ['ui.router']) + .config(['$stateProvider', function ($stateProvider) { + $stateProvider + .state('ownerNew', { + parent: 'app', + url: '/owners/new', + template: '' + }) + .state('ownerEdit', { + parent: 'app', + url: '/owners/:ownerId/edit', + template: '' + }) + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..17e97a3f --- /dev/null +++ b/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 @@ -0,0 +1,39 @@ +

Owner

+
+
+ + + First name is required. +
+ +
+ + + Last name is required. +
+ + +
+ + + Address is required. +
+ +
+ + + City is required. +
+ +
+ + + Telephone is required. +
+ +
+ +
+
+ diff --git a/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 b/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 new file mode 100644 index 00000000..55b6b253 --- /dev/null +++ b/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 @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('ownerList') + .component('ownerList', { + templateUrl: 'scripts/owner-list/owner-list.template.html', + controller: 'OwnerListController' + }); diff --git a/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 b/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 new file mode 100644 index 00000000..8229f43c --- /dev/null +++ b/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 @@ -0,0 +1,10 @@ +'use strict'; + +angular.module('ownerList') + .controller('OwnerListController', ['$http', function ($http) { + var self = this; + + $http.get('api/customer/owners').then(function (resp) { + self.owners = resp.data; + }); + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..4714a2f9 --- /dev/null +++ b/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 @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('ownerList', ['ui.router']) + .config(['$stateProvider', function ($stateProvider) { + $stateProvider + .state('owners', { + parent: 'app', + url: '/owners', + template: '' + }) + }]); \ No newline at end of file diff --git a/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 b/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 new file mode 100644 index 00000000..fc784631 --- /dev/null +++ b/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 @@ -0,0 +1,31 @@ +

Owners

+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
NameCityTelephone
+ + {{owner.firstName}} {{owner.lastName}} + + {{owner.city}}{{owner.telephone}}
diff --git a/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 b/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 new file mode 100644 index 00000000..6f5e13bb --- /dev/null +++ b/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 @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('petForm') + .component('petForm', { + templateUrl: 'scripts/pet-form/pet-form.template.html', + controller: 'PetFormController' + }); diff --git a/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 b/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 new file mode 100644 index 00000000..93bbb6c8 --- /dev/null +++ b/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 @@ -0,0 +1,52 @@ +'use strict'; + +angular.module('petForm') + .controller('PetFormController', ['$http', '$state', '$stateParams', function ($http, $state, $stateParams) { + var self = this; + var ownerId = $stateParams.ownerId || 0; + + $http.get('api/customer/petTypes').then(function (resp) { + self.types = resp.data; + }).then(function () { + + var petId = $stateParams.petId || 0; + + if (petId) { // edit + $http.get("api/customer/owners/" + ownerId + "/pets/" + petId).then(function (resp) { + self.pet = resp.data; + self.pet.birthDate = new Date(self.pet.birthDate); + self.petTypeId = "" + self.pet.type.id; + }); + } else { + $http.get('api/customer/owners/' + ownerId).then(function (resp) { + self.pet = { + owner: resp.data.firstName + " " + resp.data.lastName + }; + self.petTypeId = "1"; + }) + + } + }); + + self.submit = function () { + var id = self.pet.id || 0; + + var data = { + id: id, + name: self.pet.name, + birthDate: self.pet.birthDate, + typeId: self.petTypeId + }; + + var req; + if (id) { + req = $http.put("api/customer/owners/" + ownerId + "/pets/" + id, data); + } else { + req = $http.post("api/customer/owners/" + ownerId + "/pets", data); + } + + req.then(function () { + $state.go('ownerDetails', {ownerId: ownerId}); + }); + }; + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..70292ad6 --- /dev/null +++ b/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 @@ -0,0 +1,16 @@ +'use strict'; + +angular.module('petForm', ['ui.router']) + .config(['$stateProvider', function ($stateProvider) { + $stateProvider + .state('petNew', { + parent: 'app', + url: '/owners/:ownerId/new-pet', + template: '' + }) + .state('petEdit', { + parent: 'app', + url: '/owners/:ownerId/pets/:petId', + template: '' + }) + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..76317e55 --- /dev/null +++ b/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 @@ -0,0 +1,43 @@ +

Pet

+ +
+
+ +
+

{{$ctrl.pet.owner}}

+
+
+ +
+ +
+ + Name is required. +
+
+ +
+ +
+ + birth date is required. +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
diff --git a/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 b/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 new file mode 100644 index 00000000..6d8e9508 --- /dev/null +++ b/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 @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('vetList') + .component('vetList', { + templateUrl: 'scripts/vet-list/vet-list.template.html', + controller: 'VetListController' + }); diff --git a/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 b/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 new file mode 100644 index 00000000..4adc7fc1 --- /dev/null +++ b/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 @@ -0,0 +1,10 @@ +'use strict'; + +angular.module('vetList') + .controller('VetListController', ['$http', function ($http) { + var self = this; + + $http.get('api/vet/vets').then(function (resp) { + self.vetList = resp.data; + }); + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..bcecacc0 --- /dev/null +++ b/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 @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('vetList', ['ui.router']) + .config(['$stateProvider', function ($stateProvider) { + $stateProvider + .state('vets', { + parent: 'app', + url: '/vets', + template: '' + }) + }]); \ No newline at end of file diff --git a/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 b/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 new file mode 100644 index 00000000..e705f050 --- /dev/null +++ b/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 @@ -0,0 +1,15 @@ +

Veterinarians

+ + + + + + + + + + + + + +
NameSpecialties
{{vet.firstName}} {{vet.lastName}}{{specialty.name + ' '}}
diff --git a/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 b/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 new file mode 100644 index 00000000..0bfbcad3 --- /dev/null +++ b/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 @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('visits') + .component('visits', { + templateUrl: 'scripts/visits/visits.template.html', + controller: 'VisitsController' + }); diff --git a/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 b/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 new file mode 100644 index 00000000..d68798b3 --- /dev/null +++ b/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 @@ -0,0 +1,25 @@ +'use strict'; + +angular.module('visits') + .controller('VisitsController', ['$http', '$state', '$stateParams', '$filter', function ($http, $state, $stateParams, $filter) { + var self = this; + var petId = $stateParams.petId || 0; + var url = "api/visit/owners/" + ($stateParams.ownerId || 0) + "/pets/" + petId + "/visits"; + self.date = new Date(); + self.desc = ""; + + $http.get(url).then(function (resp) { + self.visits = resp.data; + }); + + self.submit = function () { + var data = { + date: $filter('date')(self.date, "yyyy-MM-dd"), + description: self.desc + }; + + $http.post(url, data).then(function () { + $state.go('ownerDetails', { ownerId: $stateParams.ownerId }); + }); + }; + }]); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/visits/visits.js b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/visits/visits.js new file mode 100644 index 00000000..10821aca --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/resources/static/scripts/visits/visits.js @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('visits', ['ui.router']) + .config(['$stateProvider', function ($stateProvider) { + $stateProvider + .state('visits', { + parent: 'app', + url: '/owners/:ownerId/pets/:petId/visits', + template: '' + }) + }]); diff --git a/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 b/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 new file mode 100644 index 00000000..ee099fad --- /dev/null +++ b/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 @@ -0,0 +1,27 @@ +

Visits

+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +

Previous Visits

+ + + + + +
{{v.date}}{{v.description}}
\ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.properties b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.properties new file mode 100644 index 00000000..bd6f3a81 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.properties @@ -0,0 +1,4 @@ +#List of preProcessors +preProcessors=lessCssImport +#List of postProcessors +postProcessors=less4j \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.xml new file mode 100644 index 00000000..86574683 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/main/wro/wro.xml @@ -0,0 +1,6 @@ + + + classpath:META-INF/resources/webjars/bootstrap/3.3.7-1/less/bootstrap.less + /petclinic.less + + diff --git a/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 b/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 new file mode 100644 index 00000000..864d4fbf --- /dev/null +++ b/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 @@ -0,0 +1,15 @@ +package org.springframework.samples.petclinic.api; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +@SpringBootTest +class ApiGatewayApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..62e74c49 --- /dev/null +++ b/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 @@ -0,0 +1,64 @@ +package org.springframework.samples.petclinic.api.application; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.samples.petclinic.api.dto.Visits; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.util.Collections; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class VisitsServiceClientIntegrationTest { + + private static final Integer PET_ID = 1; + + private VisitsServiceClient visitsServiceClient; + + private MockWebServer server; + + @BeforeEach + void setUp() { + server = new MockWebServer(); + visitsServiceClient = new VisitsServiceClient(WebClient.builder()); + visitsServiceClient.setHostname(server.url("/").toString()); + } + + @AfterEach + void shutdown() throws IOException { + this.server.shutdown(); + } + + @Test + void getVisitsForPets_withAvailableVisitsService() { + prepareResponse(response -> response + .setHeader("Content-Type", "application/json") + .setBody("{\"items\":[{\"id\":5,\"date\":\"2018-11-15\",\"description\":\"test visit\",\"petId\":1}]}")); + + Mono visits = visitsServiceClient.getVisitsForPets(Collections.singletonList(1)); + + assertVisitDescriptionEquals(visits.block(), PET_ID,"test visit"); + } + + + private void assertVisitDescriptionEquals(Visits visits, int petId, String description) { + assertEquals(1, visits.getItems().size()); + assertNotNull(visits.getItems().get(0)); + assertEquals(petId, visits.getItems().get(0).getPetId()); + assertEquals(description, visits.getItems().get(0).getDescription()); + } + + private void prepareResponse(Consumer consumer) { + MockResponse response = new MockResponse(); + consumer.accept(response); + this.server.enqueue(response); + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..f7ca84d4 --- /dev/null +++ b/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 @@ -0,0 +1,99 @@ +package org.springframework.samples.petclinic.api.boundary.web; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JAutoConfiguration; +import org.springframework.context.annotation.Import; +import org.springframework.samples.petclinic.api.application.CustomersServiceClient; +import org.springframework.samples.petclinic.api.application.VisitsServiceClient; +import org.springframework.samples.petclinic.api.dto.OwnerDetails; +import org.springframework.samples.petclinic.api.dto.PetDetails; +import org.springframework.samples.petclinic.api.dto.VisitDetails; +import org.springframework.samples.petclinic.api.dto.Visits; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import reactor.core.publisher.Mono; + +import java.net.ConnectException; +import java.util.Collections; + +@ExtendWith(SpringExtension.class) +@WebFluxTest(controllers = ApiGatewayController.class) +@Import({ReactiveResilience4JAutoConfiguration.class, CircuitBreakerConfiguration.class}) +class ApiGatewayControllerTest { + + @MockBean + private CustomersServiceClient customersServiceClient; + + @MockBean + private VisitsServiceClient visitsServiceClient; + + @Autowired + private WebTestClient client; + + + @Test + void getOwnerDetails_withAvailableVisitsService() { + OwnerDetails owner = new OwnerDetails(); + PetDetails cat = new PetDetails(); + cat.setId(20); + cat.setName("Garfield"); + owner.getPets().add(cat); + Mockito + .when(customersServiceClient.getOwner(1)) + .thenReturn(Mono.just(owner)); + + Visits visits = new Visits(); + VisitDetails visit = new VisitDetails(); + visit.setId(300); + visit.setDescription("First visit"); + visit.setPetId(cat.getId()); + visits.getItems().add(visit); + Mockito + .when(visitsServiceClient.getVisitsForPets(Collections.singletonList(cat.getId()))) + .thenReturn(Mono.just(visits)); + + client.get() + .uri("/api/gateway/owners/1") + .exchange() + .expectStatus().isOk() + //.expectBody(String.class) + //.consumeWith(response -> + // Assertions.assertThat(response.getResponseBody()).isEqualTo("Garfield")); + .expectBody() + .jsonPath("$.pets[0].name").isEqualTo("Garfield") + .jsonPath("$.pets[0].visits[0].description").isEqualTo("First visit"); + } + + /** + * Test Resilience4j fallback method + */ + @Test + void getOwnerDetails_withServiceError() { + OwnerDetails owner = new OwnerDetails(); + PetDetails cat = new PetDetails(); + cat.setId(20); + cat.setName("Garfield"); + owner.getPets().add(cat); + Mockito + .when(customersServiceClient.getOwner(1)) + .thenReturn(Mono.just(owner)); + + Mockito + .when(visitsServiceClient.getVisitsForPets(Collections.singletonList(cat.getId()))) + .thenReturn(Mono.error(new ConnectException("Simulate error"))); + + client.get() + .uri("/api/gateway/owners/1") + .exchange() + .expectStatus().isOk() + .expectBody() + .jsonPath("$.pets[0].name").isEqualTo("Garfield") + .jsonPath("$.pets[0].visits").isEmpty(); + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..4fe17d0b --- /dev/null +++ b/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 @@ -0,0 +1,20 @@ +package org.springframework.samples.petclinic.api.boundary.web; + +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CircuitBreakerConfiguration { + + @Bean + public CircuitBreakerRegistry circuitBreakerRegistry() { + return CircuitBreakerRegistry.ofDefaults(); + } + + @Bean + public TimeLimiterRegistry timeLimiterRegistry() { + return TimeLimiterRegistry.ofDefaults(); + } +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx new file mode 100644 index 00000000..9e88f1a8 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/jmeter/petclinic_test_plan.jmx @@ -0,0 +1,568 @@ + + + + + + false + true + false + + + + PETCLINC_HOST + localhost + = + + + PETCLINIC_PORT + 8080 + = + + + + + + + + + + + ${PETCLINC_HOST} + ${PETCLINIC_PORT} + + + + 6 + + + + + + + + Accept + application/json, text/plain, */* + + + Content-Type + application/json;charset=UTF-8 + + + Accept-Encoding + gzip, deflate, br + + + + + + + + 1 + 0 + 0 + 300 + 30 + + + + false + -1 + + continue + + + + PET_TYPE + + 1 + 6 + + false + + + + 300 + 100.0 + + + + + + + + + + + /api/customer/owners + GET + true + false + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 8 + + + + + true + + + + false + { + "firstName":"Firstname", + "lastName":"Lastname", + "address":"Adress", + "city":"City", + "telephone":"0000000000" +} + = + + + + + + + + /api/customer/owners + POST + true + false + true + false + + + + + + + + 201 + + + Assertion.response_code + false + 8 + + + + OWNER_ID + $.id + + + + + + + + + + + + + /api/gateway/owners/${OWNER_ID} + GET + true + false + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 8 + + + + + true + + + + false + { + "id":"${OWNER_ID}", + "firstName":"Firstname", + "lastName":"Lastname${OWNER_ID}", + "address":"Adress${OWNER_ID}", + "city":"City${OWNER_ID}", + "telephone":"1111111111" +} + = + + + + + + + + /api/customer/owners/${OWNER_ID} + PUT + true + false + true + false + + + + + + + + 204 + + + Assertion.response_code + false + 8 + + + + + true + + + + false + { + "name":"Pet", + "birthDate":"2018-12-31T23:00:00.000Z", + "typeId":"${PET_TYPE}" +} + = + + + + + + + + /api/customer/owners/${OWNER_ID}/pets + POST + true + false + true + false + + + + + + + + 201 + + + Assertion.response_code + false + 8 + + + + PET_ID + $.id + + + + + + 1 + + + + true + + + + false + { + "name":"Pet", + "birthDate":"2018-12-31T23:00:00.000Z", + "typeId":"${PET_TYPE}" +} + = + + + + + + + + /api/customer/owners/${OWNER_ID}/pets + POST + true + false + true + false + + + + + + + + 201 + + + Assertion.response_code + false + 8 + + + + + + true + + + + false + { + "id": ${PET_ID}, + "name":"Pet${OWNER_ID}", + "birthDate":"2018-12-31T23:00:00.000Z", + "typeId":"${PET_TYPE}" +} + = + + + + + + + + /api/customer/owners/${OWNER_ID}/pets/${PET_ID} + PUT + true + false + true + false + + + + + + + + 204 + + + Assertion.response_code + false + 8 + + + + + true + + + + false + { + "date":"2019-03-15", + "description":"Visit" +} + = + + + + + + + + /api/visit/owners/${OWNER_ID}/pets/${PET_ID}/visits + POST + true + false + true + false + + + + + + + + 201 + + + Assertion.response_code + false + 8 + + + + + 1 + + + + true + + + + false + { + "date":"2019-03-15", + "description":"Visit" +} + = + + + + + + + + /api/visit/owners/${OWNER_ID}/pets/${PET_ID}/visits + POST + true + false + true + false + + + + + + + + 201 + + + Assertion.response_code + false + 8 + + + + + + + + + + + + + /api/vet/vets + GET + true + false + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 8 + + + + + 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 + + + + + + + + + diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/resources/application-test.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/resources/application-test.yml new file mode 100644 index 00000000..cb8e4f5f --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/src/test/resources/application-test.yml @@ -0,0 +1,2 @@ +spring.cloud.config.enabled: false +eureka.client.enabled: false diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/manifest.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/manifest.yml new file mode 100644 index 00000000..dac7a7d2 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/manifest.yml @@ -0,0 +1,26 @@ +--- +applications: +- name: spring-petclinic-customers-service + memory: 1G + path: target/customers-service-3.0.1.jar + health-check-type: port + health-check-invocation-timeout: 30 + buildpacks: + - java_buildpack_offline + routes: + - route: spring-petclinic-customers-service.apps.internal + env: + JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 17.+ }}' + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}' + # Required to override SERVER_PORT env var set by buildpack and load + # default Spring application on port 8081 + # SPRING_APPLICATION_JSON: '{"server.port":8080}' + SPRING_PROFILES_ACTIVE: default + SERVER_PORT: 8080 + EUREKA_INSTANCE_SECURE_PORT_ENABLED: false + EUREKA_INSTANCE_NON_SECURE_PORT: 8080 + EUREKA_INSTANCE_PREFER_IP_ADDRESS: true + services: + - service-registry + - config-server + - mysqldb \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/pom.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/pom.xml new file mode 100644 index 00000000..c4f179bd --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/pom.xml @@ -0,0 +1,143 @@ + + + 4.0.0 + + org.springframework.samples.petclinic.client + customers-service + jar + Spring PetClinic Customers Service + + + org.springframework.samples + spring-petclinic-microservices + 3.0.1 + + + + 8081 + ${basedir}/../docker + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + io.pivotal.spring.cloud + spring-cloud-services-starter-config-client + + + io.pivotal.spring.cloud + spring-cloud-services-starter-service-registry + + + + + com.azure.spring + spring-cloud-azure-starter-jdbc-mysql + + + + + com.mysql + mysql-connector-j + runtime + + + org.hsqldb + hsqldb + runtime + + + org.jolokia + jolokia-core + + + org.projectlombok + lombok + provided + + + io.micrometer + micrometer-registry-prometheus + + + de.codecentric + chaos-monkey-spring-boot + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.assertj + assertj-core + test + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + + + + ${project.parent.version} + + + + + + + + diff --git a/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 b/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 new file mode 100644 index 00000000..867cdc19 --- /dev/null +++ b/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 @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +/** + * @author Maciej Szarlinski + */ +@EnableDiscoveryClient +@SpringBootApplication +public class CustomersServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(CustomersServiceApplication.class, args); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..fb2acaf2 --- /dev/null +++ b/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 @@ -0,0 +1,22 @@ +package org.springframework.samples.petclinic.customers.config; + +import io.micrometer.core.aop.TimedAspect; +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MetricConfig { + + @Bean + MeterRegistryCustomizer metricsCommonTags() { + return registry -> registry.config().commonTags("application", "petclinic"); + } + + @Bean + TimedAspect timedAspect(MeterRegistry registry) { + return new TimedAspect(registry); + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..4c46ab36 --- /dev/null +++ b/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 @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotBlank; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; +import org.springframework.core.style.ToStringCreator; + +/** + * Simple JavaBean domain object representing an owner. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Maciej Szarlinski + * @author Ramazan Sakin + */ +@Entity +@Table(name = "owners") +public class Owner { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Getter + private Integer id; + + @Getter + @Setter + @Column(name = "first_name") + @NotBlank + private String firstName; + + @Getter + @Setter + @Column(name = "last_name") + @NotBlank + private String lastName; + + @Getter + @Setter + @Column(name = "address") + @NotBlank + private String address; + + @Getter + @Setter + @Column(name = "city") + @NotBlank + private String city; + + @Getter + @Setter + @Column(name = "telephone") + @NotBlank + @Digits(fraction = 0, integer = 12) + private String telephone; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "owner") + private Set pets; + + protected Set getPetsInternal() { + if (this.pets == null) { + this.pets = new HashSet<>(); + } + return this.pets; + } + + public List getPets() { + final List sortedPets = new ArrayList<>(getPetsInternal()); + PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true)); + return Collections.unmodifiableList(sortedPets); + } + + public void addPet(Pet pet) { + getPetsInternal().add(pet); + pet.setOwner(this); + } + + @Override + public String toString() { + return new ToStringCreator(this) + + .append("id", this.getId()) + .append("lastName", this.getLastName()) + .append("firstName", this.getFirstName()) + .append("address", this.address) + .append("city", this.city) + .append("telephone", this.telephone) + .toString(); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..aa720f24 --- /dev/null +++ b/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 @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.model; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Repository class for Owner domain objects All method names are compliant with Spring Data naming + * conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Maciej Szarlinski + */ +public interface OwnerRepository extends JpaRepository { } diff --git a/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 b/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 new file mode 100644 index 00000000..2cb702c8 --- /dev/null +++ b/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 @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.model; + +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import org.springframework.core.style.ToStringCreator; + +/** + * Simple business object representing a pet. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Maciej Szarlinski + * @author Ramazan Sakin + */ +@Data +@Entity +@Table(name = "pets") +public class Pet { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(name = "name") + private String name; + + @Column(name = "birth_date") + @Temporal(TemporalType.DATE) + private Date birthDate; + + @ManyToOne + @JoinColumn(name = "type_id") + private PetType type; + + @ManyToOne + @JoinColumn(name = "owner_id") + @JsonIgnore + private Owner owner; + + @Override + public String toString() { + return new ToStringCreator(this) + .append("id", this.getId()) + .append("name", this.getName()) + .append("birthDate", this.getBirthDate()) + .append("type", this.getType().getName()) + .append("ownerFirstname", this.getOwner().getFirstName()) + .append("ownerLastname", this.getOwner().getLastName()) + .toString(); + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..5bd16dbe --- /dev/null +++ b/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 @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.model; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +/** + * Repository class for Pet domain objects All method names are compliant with Spring Data naming + * conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Maciej Szarlinski + */ +public interface PetRepository extends JpaRepository { + + /** + * Retrieve all {@link PetType}s from the data store. + * @return a Collection of {@link PetType}s. + */ + @Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name") + List findPetTypes(); + + @Query("FROM PetType ptype WHERE ptype.id = :typeId") + Optional findPetTypeById(@Param("typeId") int typeId); + + +} + diff --git a/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 b/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 new file mode 100644 index 00000000..80a29c13 --- /dev/null +++ b/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 @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.model; + +import lombok.Getter; +import lombok.Setter; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +/** + * @author Juergen Hoeller + * @author Ramazan Sakin + * Can be Cat, Dog, Hamster... + */ +@Entity +@Table(name = "types") +public class PetType { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Getter + @Setter + private Integer id; + + @Getter + @Setter + @Column(name = "name") + private String name; +} diff --git a/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 b/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 new file mode 100644 index 00000000..72c375a4 --- /dev/null +++ b/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 @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.web; + +import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.samples.petclinic.customers.model.Owner; +import org.springframework.samples.petclinic.customers.model.OwnerRepository; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import java.util.List; +import java.util.Optional; + +/** + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + * @author Michael Isvy + * @author Maciej Szarlinski + */ +@RequestMapping("/owners") +@RestController +@Timed("petclinic.owner") +@RequiredArgsConstructor +@Slf4j +class OwnerResource { + + private final OwnerRepository ownerRepository; + + /** + * Create Owner + */ + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Owner createOwner(@Valid @RequestBody Owner owner) { + return ownerRepository.save(owner); + } + + /** + * Read single Owner + */ + @GetMapping(value = "/{ownerId}") + public Optional findOwner(@PathVariable("ownerId") @Min(1) int ownerId) { + return ownerRepository.findById(ownerId); + } + + /** + * Read List of Owners + */ + @GetMapping + public List findAll() { + return ownerRepository.findAll(); + } + + /** + * Update Owner + */ + @PutMapping(value = "/{ownerId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void updateOwner(@PathVariable("ownerId") @Min(1) int ownerId, @Valid @RequestBody Owner ownerRequest) { + final Optional owner = ownerRepository.findById(ownerId); + final Owner ownerModel = owner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found")); + + // This is done by hand for simplicity purpose. In a real life use-case we should consider using MapStruct. + ownerModel.setFirstName(ownerRequest.getFirstName()); + ownerModel.setLastName(ownerRequest.getLastName()); + ownerModel.setCity(ownerRequest.getCity()); + ownerModel.setAddress(ownerRequest.getAddress()); + ownerModel.setTelephone(ownerRequest.getTelephone()); + log.info("Saving owner {}", ownerModel); + ownerRepository.save(ownerModel); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..6e61fc0b --- /dev/null +++ b/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 @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.web; + +import lombok.Data; + +import java.util.Date; + +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.samples.petclinic.customers.model.Pet; +import org.springframework.samples.petclinic.customers.model.PetType; + +/** + * @author mszarlinski@bravurasolutions.com on 2016-12-05. + */ +@Data +class PetDetails { + + private long id; + + private String name; + + private String owner; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date birthDate; + + private PetType type; + + PetDetails(Pet pet) { + this.id = pet.getId(); + this.name = pet.getName(); + this.owner = pet.getOwner().getFirstName() + " " + pet.getOwner().getLastName(); + this.birthDate = pet.getBirthDate(); + this.type = pet.getType(); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..a4ea3222 --- /dev/null +++ b/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 @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.web; + +import lombok.Data; + +import java.util.Date; + +import jakarta.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * @author mszarlinski@bravurasolutions.com on 2016-12-05. + */ +@Data +class PetRequest { + private int id; + + @JsonFormat(pattern = "yyyy-MM-dd") + private Date birthDate; + + @Size(min = 1) + private String name; + + private int typeId; +} diff --git a/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 b/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 new file mode 100644 index 00000000..9b1bb9e2 --- /dev/null +++ b/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 @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.customers.web; + +import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.samples.petclinic.customers.model.*; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.constraints.Min; +import java.util.List; +import java.util.Optional; + +/** + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + * @author Maciej Szarlinski + * @author Ramazan Sakin + */ +@RestController +@Timed("petclinic.pet") +@RequiredArgsConstructor +@Slf4j +class PetResource { + + private final PetRepository petRepository; + private final OwnerRepository ownerRepository; + + + @GetMapping("/petTypes") + public List getPetTypes() { + return petRepository.findPetTypes(); + } + + @PostMapping("/owners/{ownerId}/pets") + @ResponseStatus(HttpStatus.CREATED) + public Pet processCreationForm( + @RequestBody PetRequest petRequest, + @PathVariable("ownerId") @Min(1) int ownerId) { + + final Optional optionalOwner = ownerRepository.findById(ownerId); + Owner owner = optionalOwner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found")); + + final Pet pet = new Pet(); + owner.addPet(pet); + return save(pet, petRequest); + } + + @PutMapping("/owners/*/pets/{petId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void processUpdateForm(@RequestBody PetRequest petRequest) { + int petId = petRequest.getId(); + Pet pet = findPetById(petId); + save(pet, petRequest); + } + + private Pet save(final Pet pet, final PetRequest petRequest) { + + pet.setName(petRequest.getName()); + pet.setBirthDate(petRequest.getBirthDate()); + + petRepository.findPetTypeById(petRequest.getTypeId()) + .ifPresent(pet::setType); + + log.info("Saving pet {}", pet); + return petRepository.save(pet); + } + + @GetMapping("owners/*/pets/{petId}") + public PetDetails findPet(@PathVariable("petId") int petId) { + return new PetDetails(findPetById(petId)); + } + + + private Pet findPetById(int petId) { + Optional pet = petRepository.findById(petId); + if (!pet.isPresent()) { + throw new ResourceNotFoundException("Pet "+petId+" not found"); + } + return pet.get(); + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..c321a99d --- /dev/null +++ b/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 @@ -0,0 +1,13 @@ +package org.springframework.samples.petclinic.customers.web; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.NOT_FOUND) +public class ResourceNotFoundException extends RuntimeException { + + public ResourceNotFoundException(String message) { + super(message); + } + +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/application.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/application.yml new file mode 100644 index 00000000..7c95adf7 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/application.yml @@ -0,0 +1,13 @@ +spring: + application: + name: customers-service + config: + import: optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888/} + + +--- +spring: + config: + activate: + on-profile: docker + import: configserver:http://config-server:8888 diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/data.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/data.sql new file mode 100644 index 00000000..676b55f3 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/data.sql @@ -0,0 +1,31 @@ +INSERT INTO types VALUES (1, 'cat'); +INSERT INTO types VALUES (2, 'dog'); +INSERT INTO types VALUES (3, 'lizard'); +INSERT INTO types VALUES (4, 'snake'); +INSERT INTO types VALUES (5, 'bird'); +INSERT INTO types VALUES (6, 'hamster'); + +INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); +INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); +INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); +INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); +INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); +INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); +INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); +INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); +INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); +INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); + +INSERT INTO pets VALUES (1, 'Leo', '2010-09-07', 1, 1); +INSERT INTO pets VALUES (2, 'Basil', '2012-08-06', 6, 2); +INSERT INTO pets VALUES (3, 'Rosy', '2011-04-17', 2, 3); +INSERT INTO pets VALUES (4, 'Jewel', '2010-03-07', 2, 3); +INSERT INTO pets VALUES (5, 'Iggy', '2010-11-30', 3, 4); +INSERT INTO pets VALUES (6, 'George', '2010-01-20', 4, 5); +INSERT INTO pets VALUES (7, 'Samantha', '2012-09-04', 1, 6); +INSERT INTO pets VALUES (8, 'Max', '2012-09-04', 1, 6); +INSERT INTO pets VALUES (9, 'Lucky', '2011-08-06', 5, 7); +INSERT INTO pets VALUES (10, 'Mulligan', '2007-02-24', 2, 8); +INSERT INTO pets VALUES (11, 'Freddy', '2010-03-09', 5, 9); +INSERT INTO pets VALUES (12, 'Lucky', '2010-06-24', 2, 10); +INSERT INTO pets VALUES (13, 'Sly', '2012-06-08', 1, 10); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sql new file mode 100644 index 00000000..33d42ee5 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sql @@ -0,0 +1,30 @@ +DROP TABLE pets IF EXISTS; +DROP TABLE types IF EXISTS; +DROP TABLE owners IF EXISTS; + +CREATE TABLE types ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(80) +); +CREATE INDEX types_name ON types (name); + +CREATE TABLE owners ( + id INTEGER IDENTITY PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30), + address VARCHAR(255), + city VARCHAR(80), + telephone VARCHAR(12) +); +CREATE INDEX owners_last_name ON owners (last_name); + +CREATE TABLE pets ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(30), + birth_date DATE, + type_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id); +ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id); +CREATE INDEX pets_name ON pets (name); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/data.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/data.sql new file mode 100644 index 00000000..d6e91b1b --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/data.sql @@ -0,0 +1,31 @@ +INSERT IGNORE INTO types VALUES (1, 'cat'); +INSERT IGNORE INTO types VALUES (2, 'dog'); +INSERT IGNORE INTO types VALUES (3, 'lizard'); +INSERT IGNORE INTO types VALUES (4, 'snake'); +INSERT IGNORE INTO types VALUES (5, 'bird'); +INSERT IGNORE INTO types VALUES (6, 'hamster'); + +INSERT IGNORE INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); +INSERT IGNORE INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); +INSERT IGNORE INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); +INSERT IGNORE INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); +INSERT IGNORE INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); +INSERT IGNORE INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); +INSERT IGNORE INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); +INSERT IGNORE INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); +INSERT IGNORE INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); +INSERT IGNORE INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); + +INSERT IGNORE INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1); +INSERT IGNORE INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); +INSERT IGNORE INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); +INSERT IGNORE INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); +INSERT IGNORE INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); +INSERT IGNORE INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); +INSERT IGNORE INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); +INSERT IGNORE INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); +INSERT IGNORE INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); +INSERT IGNORE INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); +INSERT IGNORE INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); +INSERT IGNORE INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); +INSERT IGNORE INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql new file mode 100644 index 00000000..67eb65fe --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql @@ -0,0 +1,31 @@ +-- CREATE DATABASE IF NOT EXISTS petclinic; +-- GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc'; + +-- USE petclinic; + +CREATE TABLE IF NOT EXISTS types ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(80), + INDEX(name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS owners ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30), + address VARCHAR(255), + city VARCHAR(80), + telephone VARCHAR(20), + INDEX(last_name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS pets ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(30), + birth_date DATE, + type_id INT(4) UNSIGNED NOT NULL, + owner_id INT(4) UNSIGNED NOT NULL, + INDEX(name), + FOREIGN KEY (owner_id) REFERENCES owners(id), + FOREIGN KEY (type_id) REFERENCES types(id) +) engine=InnoDB; diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/logback-spring.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..5d03f794 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/main/resources/logback-spring.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/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 b/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 new file mode 100644 index 00000000..0bd2abb3 --- /dev/null +++ b/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 @@ -0,0 +1,77 @@ +package org.springframework.samples.petclinic.customers.web; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.customers.model.Owner; +import org.springframework.samples.petclinic.customers.model.OwnerRepository; +import org.springframework.samples.petclinic.customers.model.Pet; +import org.springframework.samples.petclinic.customers.model.PetRepository; +import org.springframework.samples.petclinic.customers.model.PetType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Maciej Szarlinski + */ +@ExtendWith(SpringExtension.class) +@WebMvcTest(PetResource.class) +@ActiveProfiles("test") +class PetResourceTest { + + @Autowired + MockMvc mvc; + + @MockBean + PetRepository petRepository; + + @MockBean + OwnerRepository ownerRepository; + + @Test + void shouldGetAPetInJSonFormat() throws Exception { + + Pet pet = setupPet(); + + given(petRepository.findById(2)).willReturn(Optional.of(pet)); + + + mvc.perform(get("/owners/2/pets/2").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.id").value(2)) + .andExpect(jsonPath("$.name").value("Basil")) + .andExpect(jsonPath("$.type.id").value(6)); + } + + private Pet setupPet() { + Owner owner = new Owner(); + owner.setFirstName("George"); + owner.setLastName("Bush"); + + Pet pet = new Pet(); + + pet.setName("Basil"); + pet.setId(2); + + PetType petType = new PetType(); + petType.setId(6); + pet.setType(petType); + + owner.addPet(pet); + return pet; + } +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/test/resources/application-test.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/test/resources/application-test.yml new file mode 100644 index 00000000..b9c699b5 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/src/test/resources/application-test.yml @@ -0,0 +1,18 @@ +spring: + cloud: + config: + enabled: false + sql: + init: + schema-locations: classpath*:db/hsqldb/schema.sql + data-locations: classpath*:db/hsqldb/data.sql + jpa: + hibernate: + ddl-auto: none + +eureka: + client: + enabled: false + +logging.level.org.springframework: INFO + diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/.gitignore b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/.gitignore new file mode 100644 index 00000000..c71ea97a --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/.gitignore @@ -0,0 +1 @@ +/.apt_generated/ diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/manifest.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/manifest.yml new file mode 100644 index 00000000..4b15bf73 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/manifest.yml @@ -0,0 +1,25 @@ +--- +applications: +- name: spring-petclinic-vets-service + # no-route: true + memory: 1G + path: target/vets-service-3.0.1.jar + health-check-type: port + health-check-invocation-timeout: 30 + buildpacks: + - java_buildpack_offline + routes: + - route: spring-petclinic-vets-service.apps.internal + env: + JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 17.+ }}' + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}' + # Required to override SERVER_PORT env var set by buildpack and load + # default Spring application on port 8081 + SPRING_PROFILES_ACTIVE: default + SERVER_PORT: 8080 + EUREKA_INSTANCE_SECURE_PORT_ENABLED: false + EUREKA_INSTANCE_NON_SECURE_PORT: 8080 + EUREKA_INSTANCE_PREFER_IP_ADDRESS: true + services: + - service-registry + - config-server \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/pom.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/pom.xml new file mode 100644 index 00000000..3f96b445 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/pom.xml @@ -0,0 +1,162 @@ + + + 4.0.0 + + org.springframework.samples.petclinic.vets + vets-service + jar + Spring PetClinic Vets Service + + + org.springframework.samples + spring-petclinic-microservices + 3.0.1 + + + + 8081 + ${basedir}/../docker + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + + + io.pivotal.spring.cloud + spring-cloud-services-starter-config-client + + + io.pivotal.spring.cloud + spring-cloud-services-starter-service-registry + + + + + + com.azure.spring + spring-cloud-azure-starter-jdbc-mysql + + + + + org.projectlombok + lombok + provided + + + javax.cache + cache-api + + + jakarta.xml.bind + jakarta.xml.bind-api + + + com.github.ben-manes.caffeine + caffeine + + + org.jolokia + jolokia-core + + + org.hsqldb + hsqldb + runtime + + + com.mysql + mysql-connector-j + runtime + + + io.micrometer + micrometer-registry-prometheus + + + de.codecentric + chaos-monkey-spring-boot + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + + + + ${project.parent.version} + + + + + + + + diff --git a/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 b/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 new file mode 100644 index 00000000..e11e1b00 --- /dev/null +++ b/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 @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.samples.petclinic.vets.system.VetsProperties; + +/** + * @author Maciej Szarlinski + */ +@EnableDiscoveryClient +@SpringBootApplication +@EnableConfigurationProperties(VetsProperties.class) +public class VetsServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(VetsServiceApplication.class, args); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..529b5a49 --- /dev/null +++ b/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 @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets.model; + +import lombok.Getter; +import lombok.Setter; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +/** + * Models a {@link Vet Vet's} specialty (for example, dentistry). + * + * @author Juergen Hoeller + * @author Ramazan Sakin + */ + +@Entity +@Table(name = "specialties") +public class Specialty { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Getter + private Integer id; + + @Getter + @Setter + @Column(name = "name") + private String name; + +} diff --git a/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 b/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 new file mode 100644 index 00000000..2cec7f89 --- /dev/null +++ b/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 @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.xml.bind.annotation.XmlElement; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; + +/** + * Simple JavaBean domain object representing a veterinarian. + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Arjen Poutsma + * @author Maciej Szarlinski + * @author Ramazan Sakin + */ +@Entity +@Table(name = "vets") +public class Vet { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Getter + @Setter + private Integer id; + + @Column(name = "first_name") + @NotBlank + @Getter + @Setter + private String firstName; + + @Column(name = "last_name") + @NotBlank + @Getter + @Setter + private String lastName; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), + inverseJoinColumns = @JoinColumn(name = "specialty_id")) + private Set specialties; + + protected Set getSpecialtiesInternal() { + if (this.specialties == null) { + this.specialties = new HashSet<>(); + } + return this.specialties; + } + + @XmlElement + public List getSpecialties() { + List sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); + PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true)); + return Collections.unmodifiableList(sortedSpecs); + } + + public int getNrOfSpecialties() { + return getSpecialtiesInternal().size(); + } + + public void addSpecialty(Specialty specialty) { + getSpecialtiesInternal().add(specialty); + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..cb22d143 --- /dev/null +++ b/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 @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets.model; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Repository class for Vet domain objects All method names are compliant with Spring Data naming + * conventions so this interface can easily be extended for Spring Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Maciej Szarlinski + */ +public interface VetRepository extends JpaRepository { +} diff --git a/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 b/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 new file mode 100644 index 00000000..2f3fb794 --- /dev/null +++ b/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 @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets.system; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +/** + * Cache could be disable in unit test. + * @author Maciej Szarlinski + */ +@Configuration +@EnableCaching +@Profile("production") +class CacheConfig { +} diff --git a/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 b/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 new file mode 100644 index 00000000..017bffe7 --- /dev/null +++ b/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 @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets.system; + +import lombok.Data; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Typesafe custom configuration. + * + * @author Maciej Szarlinski + */ +@Data +@ConfigurationProperties(prefix = "vets") +public class VetsProperties { + + private Cache cache; + + @Data + public static class Cache { + + private int ttl; + + private int heapSize; + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..eb511666 --- /dev/null +++ b/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 @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets.web; + +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import org.springframework.cache.annotation.Cacheable; +import org.springframework.samples.petclinic.vets.model.Vet; +import org.springframework.samples.petclinic.vets.model.VetRepository; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Juergen Hoeller + * @author Mark Fisher + * @author Ken Krebs + * @author Arjen Poutsma + * @author Maciej Szarlinski + */ +@RequestMapping("/vets") +@RestController +@RequiredArgsConstructor +class VetResource { + + private final VetRepository vetRepository; + + @GetMapping + @Cacheable("vets") + public List showResourcesVetList() { + return vetRepository.findAll(); + } +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/application.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/application.yml new file mode 100644 index 00000000..983a015d --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/application.yml @@ -0,0 +1,16 @@ +spring: + application: + name: vets-service + config: + import: optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888/} + cache: + cache-names: vets + profiles: + active: production + +--- +spring: + config: + activate: + on-profile: docker + import: configserver:http://config-server:8888 diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/data.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/data.sql new file mode 100644 index 00000000..e6658a52 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/data.sql @@ -0,0 +1,16 @@ +INSERT INTO vets VALUES (1, 'James', 'Carter'); +INSERT INTO vets VALUES (2, 'Helen', 'Leary'); +INSERT INTO vets VALUES (3, 'Linda', 'Douglas'); +INSERT INTO vets VALUES (4, 'Rafael', 'Ortega'); +INSERT INTO vets VALUES (5, 'Henry', 'Stevens'); +INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins'); + +INSERT INTO specialties VALUES (1, 'radiology'); +INSERT INTO specialties VALUES (2, 'surgery'); +INSERT INTO specialties VALUES (3, 'dentistry'); + +INSERT INTO vet_specialties VALUES (2, 1); +INSERT INTO vet_specialties VALUES (3, 2); +INSERT INTO vet_specialties VALUES (3, 3); +INSERT INTO vet_specialties VALUES (4, 2); +INSERT INTO vet_specialties VALUES (5, 1); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/schema.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/schema.sql new file mode 100644 index 00000000..eb916e1b --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/hsqldb/schema.sql @@ -0,0 +1,23 @@ +DROP TABLE vet_specialties IF EXISTS; +DROP TABLE vets IF EXISTS; +DROP TABLE specialties IF EXISTS; + +CREATE TABLE vets ( + id INTEGER IDENTITY PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30) +); +CREATE INDEX vets_last_name ON vets (last_name); + +CREATE TABLE specialties ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(80) +); +CREATE INDEX specialties_name ON specialties (name); + +CREATE TABLE vet_specialties ( + vet_id INTEGER NOT NULL, + specialty_id INTEGER NOT NULL +); +ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id); +ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/data.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/data.sql new file mode 100644 index 00000000..9159eca2 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/data.sql @@ -0,0 +1,16 @@ +INSERT IGNORE INTO vets VALUES (1, 'James', 'Carter'); +INSERT IGNORE INTO vets VALUES (2, 'Helen', 'Leary'); +INSERT IGNORE INTO vets VALUES (3, 'Linda', 'Douglas'); +INSERT IGNORE INTO vets VALUES (4, 'Rafael', 'Ortega'); +INSERT IGNORE INTO vets VALUES (5, 'Henry', 'Stevens'); +INSERT IGNORE INTO vets VALUES (6, 'Sharon', 'Jenkins'); + +INSERT IGNORE INTO specialties VALUES (1, 'radiology'); +INSERT IGNORE INTO specialties VALUES (2, 'surgery'); +INSERT IGNORE INTO specialties VALUES (3, 'dentistry'); + +INSERT IGNORE INTO vet_specialties VALUES (2, 1); +INSERT IGNORE INTO vet_specialties VALUES (3, 2); +INSERT IGNORE INTO vet_specialties VALUES (3, 3); +INSERT IGNORE INTO vet_specialties VALUES (4, 2); +INSERT IGNORE INTO vet_specialties VALUES (5, 1); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/schema.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/schema.sql new file mode 100644 index 00000000..a4c847e5 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/db/mysql/schema.sql @@ -0,0 +1,25 @@ +-- CREATE DATABASE IF NOT EXISTS petclinic; +-- GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc'; + +-- USE petclinic; + +CREATE TABLE IF NOT EXISTS vets ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30), + INDEX(last_name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS specialties ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(80), + INDEX(name) +) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS vet_specialties ( + vet_id INT(4) UNSIGNED NOT NULL, + specialty_id INT(4) UNSIGNED NOT NULL, + FOREIGN KEY (vet_id) REFERENCES vets(id), + FOREIGN KEY (specialty_id) REFERENCES specialties(id), + UNIQUE (vet_id,specialty_id) +) engine=InnoDB; diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/logback-spring.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..5d03f794 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/main/resources/logback-spring.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/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 b/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 new file mode 100644 index 00000000..05e29b92 --- /dev/null +++ b/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 @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.vets.web; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.vets.model.Vet; +import org.springframework.samples.petclinic.vets.model.VetRepository; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import static java.util.Arrays.asList; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Maciej Szarlinski + */ +@ExtendWith(SpringExtension.class) +@WebMvcTest(VetResource.class) +@ActiveProfiles("test") +class VetResourceTest { + + @Autowired + MockMvc mvc; + + @MockBean + VetRepository vetRepository; + + @Test + void shouldGetAListOfVets() throws Exception { + + Vet vet = new Vet(); + vet.setId(1); + + given(vetRepository.findAll()).willReturn(asList(vet)); + + mvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/test/resources/application-test.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/test/resources/application-test.yml new file mode 100644 index 00000000..684fa898 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/src/test/resources/application-test.yml @@ -0,0 +1,20 @@ +spring: + cloud: + config: + enabled: false + sql: + init: + schema-locations: classpath*:db/hsqldb/schema.sql + data-locations: classpath*:db/hsqldb/data.sql + jpa: + hibernate: + ddl-auto: none + +eureka: + client: + enabled: false + +vets: + cache: + ttl: 10 + heap-size: 10 diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/manifest.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/manifest.yml new file mode 100644 index 00000000..19407252 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/manifest.yml @@ -0,0 +1,26 @@ +--- +applications: +- name: spring-petclinic-visits-service + # no-route: true + memory: 1G + path: target/visits-service-3.0.1.jar + health-check-type: port + health-check-invocation-timeout: 30 + buildpacks: + - java_buildpack_offline + routes: + - route: spring-petclinic-visits-service.apps.internal + protocol: http1 + env: + JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 17.+ }}' + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}' + # Required to override SERVER_PORT env var set by buildpack and load + # default Spring application on port 8081 + SPRING_PROFILES_ACTIVE: default + SERVER_PORT: 8080 + EUREKA_INSTANCE_SECURE_PORT_ENABLED: false + EUREKA_INSTANCE_NON_SECURE_PORT: 8080 + EUREKA_INSTANCE_PREFER_IP_ADDRESS: true + services: + - service-registry + - config-server \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/pom.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/pom.xml new file mode 100644 index 00000000..05fed913 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/pom.xml @@ -0,0 +1,142 @@ + + + + 4.0.0 + + org.springframework.samples.petclinic.visits + visits-service + jar + Spring PetClinic Visits Service + + + org.springframework.samples + spring-petclinic-microservices + 3.0.1 + + + + 8081 + ${basedir}/../docker + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + io.pivotal.spring.cloud + spring-cloud-services-starter-config-client + + + io.pivotal.spring.cloud + spring-cloud-services-starter-service-registry + + + + + + com.azure.spring + spring-cloud-azure-starter-jdbc-mysql + + + + + org.projectlombok + lombok + provided + + + org.hsqldb + hsqldb + runtime + + + org.jolokia + jolokia-core + + + com.mysql + mysql-connector-j + runtime + + + io.micrometer + micrometer-registry-prometheus + + + de.codecentric + chaos-monkey-spring-boot + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + + + + ${project.parent.version} + + + + + + + + diff --git a/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 b/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 new file mode 100644 index 00000000..78f2963f --- /dev/null +++ b/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 @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.visits; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +/** + * @author Maciej Szarlinski + */ +@EnableDiscoveryClient +@SpringBootApplication +public class VisitsServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(VisitsServiceApplication.class, args); + } +} diff --git a/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 b/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 new file mode 100644 index 00000000..35e81f0a --- /dev/null +++ b/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 @@ -0,0 +1,22 @@ +package org.springframework.samples.petclinic.visits.config; + +import io.micrometer.core.aop.TimedAspect; +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MetricConfig { + + @Bean + MeterRegistryCustomizer metricsCommonTags() { + return registry -> registry.config().commonTags("application", "petclinic"); + } + + @Bean + TimedAspect timedAspect(MeterRegistry registry) { + return new TimedAspect(registry); + } + +} diff --git a/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 b/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 new file mode 100644 index 00000000..ba24c1ce --- /dev/null +++ b/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 @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.visits.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.validation.constraints.Size; +import java.util.Date; + +/** + * Simple JavaBean domain object representing a visit. + * + * @author Ken Krebs + * @author Maciej Szarlinski + * @author Ramazan Sakin + */ +@Entity +@Table(name = "visits") +@Builder(builderMethodName = "visit") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Visit { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Builder.Default + @Column(name = "visit_date") + @Temporal(TemporalType.TIMESTAMP) + @JsonFormat(pattern = "yyyy-MM-dd") + private Date date = new Date(); + + @Size(max = 8192) + @Column(name = "description") + private String description; + + @Column(name = "pet_id") + private int petId; + +} diff --git a/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 b/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 new file mode 100644 index 00000000..bd7a6d72 --- /dev/null +++ b/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 @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.visits.model; + +import java.util.Collection; +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Repository class for Visit domain objects All method names are compliant with Spring Data naming conventions so this interface can easily be extended for Spring + * Data See here: http://static.springsource.org/spring-data/jpa/docs/current/reference/html/jpa.repositories.html#jpa.query-methods.query-creation + * + * @author Ken Krebs + * @author Juergen Hoeller + * @author Sam Brannen + * @author Michael Isvy + * @author Maciej Szarlinski + */ +public interface VisitRepository extends JpaRepository { + + List findByPetId(int petId); + + List findByPetIdIn(Collection petIds); +} diff --git a/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 b/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 new file mode 100644 index 00000000..1115dd7c --- /dev/null +++ b/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 @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.visits.web; + +import java.util.List; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; + +import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.samples.petclinic.visits.model.Visit; +import org.springframework.samples.petclinic.visits.model.VisitRepository; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Juergen Hoeller + * @author Ken Krebs + * @author Arjen Poutsma + * @author Michael Isvy + * @author Maciej Szarlinski + * @author Ramazan Sakin + */ +@RestController +@RequiredArgsConstructor +@Slf4j +@Timed("petclinic.visit") +class VisitResource { + + private final VisitRepository visitRepository; + + @PostMapping("owners/*/pets/{petId}/visits") + @ResponseStatus(HttpStatus.CREATED) + public Visit create( + @Valid @RequestBody Visit visit, + @PathVariable("petId") @Min(1) int petId) { + + visit.setPetId(petId); + log.info("Saving visit {}", visit); + return visitRepository.save(visit); + } + + @GetMapping("owners/*/pets/{petId}/visits") + public List read(@PathVariable("petId") @Min(1) int petId) { + return visitRepository.findByPetId(petId); + } + + @GetMapping("pets/visits") + public Visits read(@RequestParam("petId") List petIds) { + final List byPetIdIn = visitRepository.findByPetIdIn(petIds); + return new Visits(byPetIdIn); + } + + @Value + static class Visits { + List items; + } +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/application.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/application.yml new file mode 100644 index 00000000..ada02194 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/application.yml @@ -0,0 +1,13 @@ +spring: + application: + name: visits-service + config: + import: optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888/} + + +--- +spring: + config: + activate: + on-profile: docker + import: configserver:http://config-server:8888 diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/data.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/data.sql new file mode 100644 index 00000000..29bc762e --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/data.sql @@ -0,0 +1,4 @@ +INSERT INTO visits VALUES (1, 7, '2013-01-01', 'rabies shot'); +INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot'); +INSERT INTO visits VALUES (3, 8, '2013-01-03', 'neutered'); +INSERT INTO visits VALUES (4, 7, '2013-01-04', 'spayed'); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/schema.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/schema.sql new file mode 100644 index 00000000..9714a580 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/hsqldb/schema.sql @@ -0,0 +1,10 @@ +DROP TABLE visits IF EXISTS; + +CREATE TABLE visits ( + id INTEGER IDENTITY PRIMARY KEY, + pet_id INTEGER NOT NULL, + visit_date DATE, + description VARCHAR(8192) +); + +CREATE INDEX visits_pet_id ON visits (pet_id); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/data.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/data.sql new file mode 100644 index 00000000..d57dce6e --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/data.sql @@ -0,0 +1,4 @@ +INSERT IGNORE INTO visits VALUES (1, 7, '2010-03-04', 'rabies shot'); +INSERT IGNORE INTO visits VALUES (2, 8, '2011-03-04', 'rabies shot'); +INSERT IGNORE INTO visits VALUES (3, 8, '2009-06-04', 'neutered'); +INSERT IGNORE INTO visits VALUES (4, 7, '2008-09-04', 'spayed'); diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/schema.sql b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/schema.sql new file mode 100644 index 00000000..c9e76dcb --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/db/mysql/schema.sql @@ -0,0 +1,12 @@ +-- CREATE DATABASE IF NOT EXISTS petclinic; +-- GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc'; + +-- USE petclinic; + +CREATE TABLE IF NOT EXISTS visits ( + id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + pet_id INT(4) UNSIGNED NOT NULL, + visit_date DATE, + description VARCHAR(8192), + FOREIGN KEY (pet_id) REFERENCES pets(id) +) engine=InnoDB; diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/logback-spring.xml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..5d03f794 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/main/resources/logback-spring.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/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 b/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 new file mode 100644 index 00000000..25d33f24 --- /dev/null +++ b/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 @@ -0,0 +1,61 @@ +package org.springframework.samples.petclinic.visits.web; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.samples.petclinic.visits.model.VisitRepository; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + + +import static java.util.Arrays.asList; +import static org.mockito.BDDMockito.given; +import static org.springframework.samples.petclinic.visits.model.Visit.visit; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(VisitResource.class) +@ActiveProfiles("test") +class VisitResourceTest { + + @Autowired + MockMvc mvc; + + @MockBean + VisitRepository visitRepository; + + @Test + void shouldFetchVisits() throws Exception { + given(visitRepository.findByPetIdIn(asList(111, 222))) + .willReturn( + asList( + visit() + .id(1) + .petId(111) + .build(), + visit() + .id(2) + .petId(222) + .build(), + visit() + .id(3) + .petId(222) + .build() + ) + ); + + mvc.perform(get("/pets/visits?petId=111,222")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.items[0].id").value(1)) + .andExpect(jsonPath("$.items[1].id").value(2)) + .andExpect(jsonPath("$.items[2].id").value(3)) + .andExpect(jsonPath("$.items[0].petId").value(111)) + .andExpect(jsonPath("$.items[1].petId").value(222)) + .andExpect(jsonPath("$.items[2].petId").value(222)); + } +} diff --git a/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/test/resources/application-test.yml b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/test/resources/application-test.yml new file mode 100644 index 00000000..b9c699b5 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/src/test/resources/application-test.yml @@ -0,0 +1,18 @@ +spring: + cloud: + config: + enabled: false + sql: + init: + schema-locations: classpath*:db/hsqldb/schema.sql + data-locations: classpath*:db/hsqldb/data.sql + jpa: + hibernate: + ddl-auto: none + +eureka: + client: + enabled: false + +logging.level.org.springframework: INFO + diff --git a/scenarios/shared/bicep/container-registry.bicep b/scenarios/shared/bicep/container-registry.bicep index 83ef5722..e6dd4549 100644 --- a/scenarios/shared/bicep/container-registry.bicep +++ b/scenarios/shared/bicep/container-registry.bicep @@ -160,6 +160,24 @@ param cMKKeyVersion string = '' @description('Conditional. User assigned identity to use when fetching the customer managed key. Note, CMK requires the \'acrSku\' to be \'Premium\'. Required if \'cMKKeyName\' is not empty.') param cMKUserAssignedIdentityResourceId string = '' +@description('Optional. The name of the agent pool. This agent pool will be used to build docker image to be deployed.') +param agentPoolName string = 'default' + +@description('Optional. The number of agents in the agent pool.') +param agentPoolCount int = 1 + +@description('Optional. The tier of the agent pool.') +@allowed([ + 'S1' + 'S2' + 'S3' + 'I6' +]) +param agentPoolTier string = 'S2' + +@description('The resource ID of the subnet to which the agent pool will be connected.') +param agentPoolSubnetId string + var diagnosticsLogsSpecified = [for category in filter(diagnosticLogCategoriesToEnable, item => item != 'allLogs'): { category: category enabled: true @@ -261,6 +279,19 @@ resource registry_diagnosticSettingName 'Microsoft.Insights/diagnosticsettings@2 scope: registry } +@description('This agentPool associated with the Azure subnet, will be used to build docker image in ACR build task.') +resource registry_agentPool 'Microsoft.ContainerRegistry/registries/agentPools@2019-06-01-preview' = { + parent: registry + name: agentPoolName + location: location + properties: { + count: agentPoolCount + os: 'Linux' + tier: agentPoolTier + virtualNetworkSubnetResourceId: agentPoolSubnetId + } +} + @description('The Name of the Azure container registry.') output name string = registry.name @@ -278,3 +309,6 @@ output systemAssignedPrincipalId string = systemAssignedIdentity && contains(reg @description('The location the resource was deployed into.') output location string = registry.location + +@description('The resource ID of Azure container registry agent pool, used for docker image build.') +output agentPoolName string = registry_agentPool.name diff --git a/scenarios/shared/bicep/naming/naming-rules.jsonc b/scenarios/shared/bicep/naming/naming-rules.jsonc index 2a1c4d01..a7ec7cd0 100644 --- a/scenarios/shared/bicep/naming/naming-rules.jsonc +++ b/scenarios/shared/bicep/naming/naming-rules.jsonc @@ -34,7 +34,8 @@ "azureAISearch": "srch", "documentIntelligence": "di", "eventGridSystemTopic": "egst", - "eventGridSubscription": "evgs" + "eventGridSubscription": "evgs", + "mysqlServer": "mysql" }, //copied from here: https://github.com/nianton/azure-naming/blob/main/datafiles/regionAbbreviations.json diff --git a/scenarios/shared/bicep/naming/naming.module.bicep b/scenarios/shared/bicep/naming/naming.module.bicep index 9c3c5812..84455a28 100644 --- a/scenarios/shared/bicep/naming/naming.module.bicep +++ b/scenarios/shared/bicep/naming/naming.module.bicep @@ -82,6 +82,7 @@ var resourceNames = { eventGridSystemTopic: replace ( namingBaseUnique, resourceTypeToken, naming.resourceTypeAbbreviations.eventGridSystemTopic ) eventGridSystemTopicPep: '${naming.resourceTypeAbbreviations.privateEndpoint}-${replace(namingBaseUnique, resourceTypeToken, naming.resourceTypeAbbreviations.eventGridSystemTopic)}' eventGridSubscription: replace ( namingBaseUnique, resourceTypeToken, naming.resourceTypeAbbreviations.eventGridSubscription ) + mysqlServer: take ( toLower( replace ( replace(namingBaseUnique, resourceTypeToken, naming.resourceTypeAbbreviations.mysqlServer), '-', '' ) ), 24 ) } output resourcesNames object = resourceNames