diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/04-deploy-apps.md b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/04-deploy-apps.md index d7b5495e..67b9359c 100644 --- a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/04-deploy-apps.md +++ b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/04-deploy-apps.md @@ -88,3 +88,8 @@ This documentation guides how to deploy the PetClinic microservices. 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.* + + +## Next step + +:arrow_forward: [Setup CI/CD pipeline](./05-github-action.md) \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/05-github-action.md b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/05-github-action.md new file mode 100644 index 00000000..259ed1d1 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/05-github-action.md @@ -0,0 +1,63 @@ +# Setup CI/CD Pipeline + +With the application code already deployed to Azure Container Apps, the next step is to establish a CI/CD pipeline. This pipeline will automate the process of streaming subsequent code changes directly to Azure Container Apps. This setup ensures that every update is seamlessly integrated and deployed, enhancing efficiency and reducing manual effort. + +## Expected Results + +Setting up a GitHub Action for a CI/CD pipeline to deploy code changes to Azure Container Apps streamlines the development process, ensuring rapid and reliable delivery of updates. + +## Steps + +1. Create the Identity Used by GitHub Actions. + ```bash + APPID_GITHUB_ACTION=$(az ad app create --display-name github-action --query appId -o tsv) + az ad sp create --id $APPID_GITHUB_ACTION + ``` + +1. To allow the Application authenticate to Azure, set up the [federated identity credential](https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0). + First, update `[Your-GitHub-Organization]` and `[Your-Release-Branch]` to the [federated-credential.json](../modules/federated-credential.json). + + ```bash + az ad app federated-credential create --id $APPID_GITHUB_ACTION --parameters modules/federated-credential.json + ``` + +1. Assign your application necessary role to run the CI/CD pipeline. + + Retrieve your spoke resource group id + ```bash + RESOURCEID_RESOURCEGROUP_SPOKE=$(az group show -n $RESOURCENAME_RESOURCEGROUP_SPOKE --query id -o tsv) + echo RESOURCEID_RESOURCEGROUP_SPOKE=$RESOURCEID_RESOURCEGROUP_SPOKE + ``` + + Open [modules/role-definition.json](../modules/role-definition.json), update `Your-Spoke-Resource-Group-Id` to your `RESOURCEID_RESOURCEGROUP_SPOKE`. + + ```bash + ID_ROLEDEFINITION=$(az role definition create --role-definition @modules/role-definition.json --query id -o tsv) + az role assignment create --role $ID_ROLEDEFINITION --scope $RESOURCEID_RESOURCEGROUP_SPOKE --assignee $APPID_GITHUB_ACTION + ``` + +1. Prepare variable settings in GitHub repository by setting the Azure information to repository. + + ```bash + AZURE_TENANT_ID=$(az account show --query tenantId -o tsv) + AZURE_SUBSCRIPTION_ID=$(az account show --query id -o tsv) + echo AZURE_CLIENT_ID=$APPID_GITHUB_ACTION && \ + AZURE_TENANT_ID=$AZURE_TENANT_ID && \ + AZURE_SUBSCRIPTION_ID=$AZURE_SUBSCRIPTION_ID && \ + AZURE_RESOURCE_GROUP=$RESOURCENAME_RESOURCEGROUP_SPOKE + ``` + Navigate to the GitHub repository setting page, select `Security`/`Secrets and variables`/`Actions`. Set the `AZURE_CLIENT_ID` and `AZURE_TENANT_ID` to the secrets, `AZURE_SUBSCRIPTION_ID` and `AZURE_RESOURCE_GROUP` to the variables, according to above output. + + ![Set up Env](./github-action-env.png) + +1. Create the workflow file. + Edit the [petclinic-deploy.yml](../modules/petclinic-deploy.yml), replace `[Your-Release-Branch-Name]` to your working branch. It should be consistent with the value set in step 2. + Move the [petclinic-deploy.yml](../modules/petclinic-deploy.yml) to [.github/workflows](../../../../../../.github/workflows/) folder, commit the changes and push to remote your working branch. + +1. Navigate to GitHub Actions page and check the running state of the workflow. + + ![Github Actions](../docs/github-action-workflow.png) + +## Next step + +:arrow_forward: [Update Spring Cloud dependencies](./06-Update-Spring-Cloud-dependencies.md) \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/06-Update-Spring-Cloud-dependencies.md b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/06-Update-Spring-Cloud-dependencies.md new file mode 100644 index 00000000..68840ce5 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/06-Update-Spring-Cloud-dependencies.md @@ -0,0 +1,54 @@ +# Update Spring Cloud dependencies + +As the given PetClinic Microservices previously were deployed on Pivotal Cloud Foundry. They uses the Pivotal Spring Cloud dependencies to interact with Config Server and Service Registry. After migrate to Azure Container Apps, when interacting with [Java components](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/announcing-the-general-availability-of-java-experiences-on-azure/ba-p/4238294), you can change the dependencies to the community version. + +## Expected Results + +The PetClinic Microservices uses community version dependencies to talk with Spring Cloud Config Server and Eureka Server. + +## Steps + +1. Remove the `spring-cloud-services-dependencies`. + Navigate to [src/pom.xml](../src/pom.xml) and remove the `spring-cloud-services-dependencies` + + ```diff + - + - io.pivotal.spring.cloud + - spring-cloud-services-dependencies + - ${spring-cloud-services.version} + - pom + - import + - + ``` + +1. Replace the Config Server dependency and Service Registry dependency. + Update the follow pom files: + - [src/spring-petclinic-api-gateway/pom.xml](../src/spring-petclinic-api-gateway/pom.xml) + - [src/spring-petclinic-customers-service/pom.xml](../src/spring-petclinic-customers-service/pom.xml) + - [src/spring-petclinic-vets-service/pom.xml](../src/spring-petclinic-vets-service/pom.xml) + - [src/spring-petclinic-visits-service/pom.xml](../src/spring-petclinic-visits-service/pom.xml) + + ```diff + + - io.pivotal.spring.cloud + - spring-cloud-services-starter-config-client + + org.springframework.cloud + + spring-cloud-config-client + + + - io.pivotal.spring.cloud + - spring-cloud-services-starter-service-registry + + org.springframework.cloud + + spring-cloud-starter-netflix-eureka-client + + ``` + +1. Commit and push above changes to remote and trigger the pipeline. + +1. Navigate to GitHub Actions page and check the running state of the workflow. + + ![Github Actions](../docs/github-action-workflow.png) + +1. After the pipeline successfully finished, view your application. + + 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.* \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/github-action-env.png b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/github-action-env.png new file mode 100644 index 00000000..99312dda Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/github-action-env.png differ diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/github-action-workflow.png b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/github-action-workflow.png new file mode 100644 index 00000000..23b1bd47 Binary files /dev/null and b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/docs/github-action-workflow.png differ diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/federated-credential.json b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/federated-credential.json new file mode 100644 index 00000000..87df470b --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/federated-credential.json @@ -0,0 +1,9 @@ +{ + "name": "GitHubAction", + "issuer": "https://token.actions.githubusercontent.com", + "subject": "repo:[Your-GitHub-Organization]/aca-landing-zone-accelerator:ref:refs/heads/[Your-Release-Branch]", + "description": "Github Action Auth", + "audiences": [ + "api://AzureADTokenExchange" + ] +} \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/petclinic-deploy.yml b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/petclinic-deploy.yml new file mode 100644 index 00000000..2a3914c6 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/petclinic-deploy.yml @@ -0,0 +1,122 @@ +# This workflow will deploy the PetClinic application to Azure Container Apps +name: PetClinic Deploy + +on: + # When you directly push in the main branch + push: + branches: + - '[Your-Release-Branch-Name]' + paths: + - 'scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/**' + - '!scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/**.md' + - '.github/workflows/petclinic-deploy.yml' + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Login via Azure CLI + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + + - name: Prepare env variables + uses: azure/CLI@v2 + with: + azcliversion: latest + inlineScript: | + export ENVIRONMENT_NAME=$(az deployment group show -n acalza01-appplat -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.containerAppsEnvironmentName.value -o tsv) + export RESOURCEID_IDENTITY_ACR=$(az deployment group show -n acalza01-dependencies -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.containerRegistryUserAssignedIdentityId.value -o tsv) + export REGISTRYNAME_ACR=$(az deployment group show -n acalza01-dependencies -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.containerRegistryName.value -o tsv) + export LOGINSERVER_ACR=$(az deployment group show -n acalza01-dependencies -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.containerRegistryLoginServer.value -o tsv) + export RESOURCENAME_AGENTPOOL=$(az deployment group show -n acalza01-dependencies -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.containerRegistryAgentPoolName.value -o tsv) + export RESOURCEID_EUREKA=$(az deployment group show -n acalza01-appplat-java -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.eurekaId.value -o tsv) + export RESOURCEID_CONFIGSERVER=$(az deployment group show -n acalza01-appplat-java -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.configServerId.value -o tsv) + export RESOURCEID_MYSQL_DATABASE=$(az deployment group show -n acalza01-appplat-java -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.databaseId.value -o tsv) + export RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID=$(az deployment group show -n acalza01-appplat-java -g ${{ vars.AZURE_RESOURCE_GROUP }} --query properties.outputs.userAssignedIdentityClientId.value -o tsv) + echo ENVIRONMENT_NAME=$ENVIRONMENT_NAME >> $GITHUB_ENV + echo RESOURCEID_IDENTITY_ACR=$RESOURCEID_IDENTITY_ACR >> $GITHUB_ENV + echo REGISTRYNAME_ACR=$REGISTRYNAME_ACR >> $GITHUB_ENV + echo LOGINSERVER_ACR=$LOGINSERVER_ACR >> $GITHUB_ENV + echo RESOURCENAME_AGENTPOOL=$RESOURCENAME_AGENTPOOL >> $GITHUB_ENV + echo RESOURCEID_MYSQL_DATABASE=$RESOURCEID_MYSQL_DATABASE >> $GITHUB_ENV + echo RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID=$RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID >> $GITHUB_ENV + echo RESOURCEID_EUREKA=$RESOURCEID_EUREKA >> $GITHUB_ENV + echo RESOURCEID_CONFIGSERVER=$RESOURCEID_CONFIGSERVER >> $GITHUB_ENV + + - name: Prepare image version variable + run: echo "IMAGE_TAG=ga-${{ github.run_number }}-$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV + + - uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + + - name: Build Jar with Maven + run: mvn clean package + working-directory: scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src + + - name: Queue Build for application vets-service + uses: azure/cli@v2 + with: + azcliversion: latest + inlineScript: | + pwd && \ + az acr build -t spring-petclinic-vets-service:${{ env.IMAGE_TAG }} \ + -r ${{ env.REGISTRYNAME_ACR }} scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-vets-service/target/docker \ + --build-arg ARTIFACT_NAME=vets-service-3.0.1 \ + --build-arg EXPOSED_PORT=8080 \ + --agent-pool ${{ env.RESOURCENAME_AGENTPOOL }} --debug + + - name: Queue Build for application visits-service + uses: azure/cli@v2 + with: + azcliversion: latest + inlineScript: | + az acr build -t spring-petclinic-visits-service:${{ env.IMAGE_TAG }} \ + -r ${{ env.REGISTRYNAME_ACR }} scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-visits-service/target/docker \ + --build-arg ARTIFACT_NAME=visits-service-3.0.1 \ + --build-arg EXPOSED_PORT=8080 \ + --agent-pool ${{ env.RESOURCENAME_AGENTPOOL }} + + - name: Queue Build for application customers-service + uses: azure/cli@v2 + with: + azcliversion: latest + inlineScript: | + az acr build -t spring-petclinic-customers-service:${{ env.IMAGE_TAG }} \ + -r ${{ env.REGISTRYNAME_ACR }} scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-customers-service/target/docker \ + --build-arg ARTIFACT_NAME=customers-service-3.0.1 \ + --build-arg EXPOSED_PORT=8080 \ + --agent-pool ${{ env.RESOURCENAME_AGENTPOOL }} + + - name: Queue Build for application api-gateway + uses: azure/cli@v2 + with: + azcliversion: latest + inlineScript: | + az acr build -t spring-petclinic-api-gateway:${{ env.IMAGE_TAG }} \ + -r ${{ env.REGISTRYNAME_ACR }} scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/src/spring-petclinic-api-gateway/target/docker \ + --build-arg ARTIFACT_NAME=api-gateway-3.0.1 \ + --build-arg EXPOSED_PORT=8080 \ + --agent-pool ${{ env.RESOURCENAME_AGENTPOOL }} + + - name: Deploy image to Azure Container Apps + uses: azure/arm-deploy@v1 + with: + subscriptionId: ${{ vars.AZURE_SUBSCRIPTION_ID }} + resourceGroupName: ${{ vars.AZURE_RESOURCE_GROUP }} + template: scenarios/aca-internal/bicep/sample-apps/cloud-foundry-spring-boot-application/modules/petclinic.bicep + parameters: 'managedEnvironmentsName=${{ env.ENVIRONMENT_NAME }} eurekaId=${{ env.RESOURCEID_EUREKA }} configServerId=${{ env.RESOURCEID_CONFIGSERVER }} mysqlDBId=${{ env.RESOURCEID_MYSQL_DATABASE }} mysqlUserAssignedIdentityClientId=${{ env.RESOURCEID_MYSQL_USERASSIGNEDIDENTITY_CLIENTID }} acrRegistry=${{ env.LOGINSERVER_ACR }} imageTag=${{ env.IMAGE_TAG }} acrIdentityId=${{ env.RESOURCEID_IDENTITY_ACR }}' + failOnStdErr: false \ No newline at end of file diff --git a/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/role-definition.json b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/role-definition.json new file mode 100644 index 00000000..9d1ac4e8 --- /dev/null +++ b/scenarios/aca-internal/bicep/sample-apps/PCF-spring-boot/modules/role-definition.json @@ -0,0 +1,23 @@ +{ + "description": "Used for Azure Container Apps deploy from code, including build image uses Azure Container Registry and deploy to Azure Container Apps", + "name": "ACR build and deploy to ACA", + "actions": [ + "*/read", + "Microsoft.Resources/deployments/write", + "Microsoft.Resources/deployments/validate/action", + "Microsoft.ContainerRegistry/registries/listBuildSourceUploadUrl/action", + "Microsoft.ContainerRegistry/registries/runs/listLogSasUrl/action", + "Microsoft.ContainerRegistry/registries/builds/cancel/action", + "Microsoft.ContainerRegistry/registries/scheduleRun/action", + "Microsoft.App/containerApps/write", + "Microsoft.app/managedEnvironments/join/action", + "Microsoft.App/managedEnvironments/javaComponents/write", + "Microsoft.App/containerApps/listSecrets/action", + "Microsoft.ServiceLinker/linkers/write", + "Microsoft.ManagedIdentity/userAssignedIdentities/assign/action" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [], + "AssignableScopes": ["Your-Spoke-Resource-Group-Id"] +} \ No newline at end of file