Migrating an Springboot Java API: Part 4 - Azure Release
Tags: java, docker, azure, aws
Published: 2023-11-12 12:38:52

On the [previous post](https://dev.to/dev-laura/migrating-an-springboot-java-api-part-3-aws-elasticbeanstalk-2a8k) I used AWS Elastic Beanstalk to pull and run 2 dockerized Java APIs in a fully fledged application.
In this guide I will show how I used Azure DevOps release to continously deploy new versions of the APIs into AWS.

## Pre-requisites
- Azure DevOps agents (I used `ubuntu-latest` pools)
- An AWS account with EB (Elastic Beanstalk) access
- Setup [AWS Toolkit for Microsoft Azure DevOps](https://docs.aws.amazon.com/vsts/latest/userguide/getting-started.html)

## IAM role
In order to allow my Azure releases to deploy new AWS Elastic Beanstalk versions, I created IAM role with the following permission policies:
```
AdministratorAccess-AWSElasticBeanstalk
AmazonEC2ContainerRegistryFullAccess
EC2InstanceProfileForImageBuilderECRContainerBuilds
AmazonSSMFullAccess
AmazonEC2FullAccess
AWSElasticBeanstalkMulticontainerDocker
AWSElasticBeanstalkRoleECS
```

## Changing the pipelines to include the deployment information
In the previous post I created a `deployment` folder in my solution repository, so now I changed the existing `azure-pipelines.yml` to publish the deployment into a different artifact.

```yaml
trigger:
  - main

resources:
  - repo: self

stages:
  - stage: ReplaceVariables
    displayName: Replace variables
    jobs:
      - job: ReplaceAzureVariables
        displayName: Replace Azure variables
        pool:
          vmImage: ubuntu-latest
        steps:
        - task: replacetokens@5
          displayName: Replace Tokens
          inputs:
            targetFiles: |
              **/application.yml
              **/application.properties
              deployment/**

            encoding: 'auto'
            tokenPattern: 'doublebraces'
            writeBOM: true
            actionOnMissing: 'warn'
            keepToken: false
            actionOnNoFiles: 'continue'
            enableTransforms: false
            enableRecursion: false
            useLegacyPattern: false
            enableTelemetry: false

        - task: CopyFiles@2
          displayName: Copy deployment files
          inputs:
            Contents: 'deployment/**'
            TargetFolder: '$(Build.ArtifactStagingDirectory)'

        - publish: $(Build.ArtifactStagingDirectory)/deployment
          displayName: Publish deployment files
          artifact: deployment

        - task: CopyFiles@2
          displayName: Copy all files 
          inputs:
            Contents: '**'
            TargetFolder: '$(Build.ArtifactStagingDirectory)'

        - publish: $(Build.ArtifactStagingDirectory)
          displayName: Publish all files
          artifact: drop

  - stage: CustomerDockerBuildPublish
    displayName: Customer API
    dependsOn: ReplaceVariables
    jobs:
      - job: Build_and_Push
        displayName: Customer - Build & Push Docker image
        pool:
          vmImage: ubuntu-latest
        steps:
          # Skip source code checkout and reuse sources
          - checkout: none
          # Download the artifact from the ReplaceVariables stage
          - task: DownloadPipelineArtifact@2
            inputs:
              buildType: 'current'
              artifact: drop
              targetPath: '$(Build.SourcesDirectory)'

          - task: Docker@2
            displayName: Customer - Build Customer API Docker image
            inputs:
              command: build
              dockerfile: './Customer.Dockerfile'
              buildContext: '$(Build.SourcesDirectory)'
              repository: $(CUSTOMER_DOCKER_REPOSITORY_NAME)
              tags: |
                $(Build.BuildNumber)

          - task: ECRPushImage@1
            displayName: Customer - Push Admin API Docker image
            inputs:
              awsCredentials: 'my-azure-service-connection'
              regionName: '$(AWS_REGION)'
              imageSource: 'imagename'
              sourceImageName: '$(CUSTOMER_DOCKER_REPOSITORY_NAME)'
              sourceImageTag: '$(Build.BuildNumber)'
              pushTag: '$(Build.BuildNumber)'
              repositoryName: '$(CUSTOMER_DOCKER_REPOSITORY_NAME)'
              logRequest: true
              logResponse: true

  - stage: AdminDockerBuildPublish
    displayName: Admin API
    dependsOn: ReplaceVariables
    jobs:
      - job: Build_and_Push
        displayName: Admin - Build & Push Docker image
        pool:
          vmImage: ubuntu-latest
        steps:
          # Skip source code checkout and reuse sources
          - checkout: none
          # Download the artifact from the ReplaceVariables stage
          - task: DownloadPipelineArtifact@2
            inputs:
              buildType: 'current'
              artifact: drop
              targetPath: '$(Build.SourcesDirectory)'

          - task: Docker@2
            displayName: Admin - Build Admin API Docker image
            inputs:
              command: build
              dockerfile: './Admin.Dockerfile'
              buildContext: '$(Build.SourcesDirectory)'
              repository: $(ADMIN_DOCKER_REPOSITORY_NAME)
              tags: |
                $(Build.BuildNumber)

          - task: ECRPushImage@1
            displayName: Admin - Push Admin API Docker image
            inputs:
              awsCredentials: 'my-azure-service-connection'
              regionName: '$(AWS_REGION)'
              imageSource: 'imagename'
              sourceImageName: '$(ADMIN_DOCKER_REPOSITORY_NAME)'
              sourceImageTag: '$(Build.BuildNumber)'
              pushTag: '$(Build.BuildNumber)'
              repositoryName: '$(ADMIN_DOCKER_REPOSITORY_NAME)'
              logRequest: true
              logResponse: true

```

## Change the release trigger
Then I changed the release to be triggered when new builds from `main` are successful.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lyh5ympxqbfnw58fx3k.png)



## Add Azure DevOps Release
Since we want the image size to be linked to the `BuildNumber` I have changed the release name configuration to be:

![Release Name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pd7h7zw4uefs1ai74xqb.png)


```yaml
steps:
- task: UsePythonVersion@0
  displayName: 'Use Python 3.7'
  inputs:
    versionSpec: 3.7

- script: 'pip install --upgrade pip'
  workingDirectory: '$(System.DefaultWorkingDirectory)/_MyJavaSolutionAPI/deployment'
  displayName: 'Command Line Script - Install pip'

- script: 'pip install awsebcli --upgrade --user'
  workingDirectory: '$(System.DefaultWorkingDirectory)/_MyJavaSolutionAPI/deployment'
  displayName: 'Command Line Script- Install awsebcli '


- task: AmazonWebServices.aws-vsts-tools.AWSShellScript.AWSShellScript@1
  displayName: 'AWS Shell Script - Elastic Beanstalk init'
  inputs:
    awsCredentials: 'my-aws-azure-service-connection'
    regionName: '$(AWS_REGION)'
    scriptType: inline
    inlineScript: 'eb init $(AWS_BEANSTALK_ENVIRONMENT_NAME) --platform "$(AWS_PLATFORM)" --tags $(AWS_TAGS) --keyname $(AWS_BEANSTALK_KEYNAME_NAME) --region $(AWS_REGION)'
    disableAutoCwd: true
    workingDirectory: '$(System.DefaultWorkingDirectory)/_MyJavaSolutionAPI/deployment'


- task: AmazonWebServices.aws-vsts-tools.AWSShellScript.AWSShellScript@1
  displayName: 'AWS Shell Script - Elastic Beanstalk deploy'
  inputs:
    awsCredentials: 'my-aws-azure-service-connection'
    regionName: '$(AWS_REGION)'
    scriptType: inline
    inlineScript: 'eb deploy $(AWS_BEANSTALK_ENVIRONMENT_NAME) --label "$(Release.ReleaseName)" --message "$(Release.ReleaseDescription)" --process --region $(AWS_REGION)'
    disableAutoCwd: true
    workingDirectory: '$(System.DefaultWorkingDirectory)/_MyJavaSolutionAPI/deployment'
```


![Release steps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ay8om45dxuhi9syan3o.png)


And with that we have a completed CI/CD pipeline in Azure DevOps
This concludes my post series "Migrating an Springboot Java API".
Creating a dockerized version of a Java 1.8 Springboot solution containing 2 APIs, then creating a CI/CD pipeline in Azure DevOps and using AWS Elastic Beanstalk to host and orchestrate the running server.