Migrating an Springboot Java API: Part 2 - Azure Build
Tags: java, docker, azure, aws
Published: 2023-11-05 12:57:25

On the [previous post](https://dev.to/dev-laura/migrating-an-springboot-java-api-part-1-docker-6n2) I dockerized an Java 1.8 Springboot solution.
In this guide I will show how I used Azure DevOps pipelines to build and push images into AWS.

## Pre-requisites
- Azure DevOps agents (I used `ubuntu-latest` pools)
- An AWS account with ECR (Elastic Container Registry) 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 builds to push images into AWS, I created an IAM role with the following permission policies:
```
AmazonEC2ContainerRegistryFullAccess
AmazonElasticContainerRegistryPublicFullAccess
EC2InstanceProfileForImageBuilderECRContainerBuilds
```

## Changing the hard-coded configurations with variables
In the configuration files of the project, `application.yml` and           `application.properties` I then substituted it for the variables I created in my Variable groups.
For example, for the Flyway database migration configuration in the `application.properties` looks like:
```yaml
#Flyway properties
spring.flyway.enabled=true
spring.flyway.url=jdbc:{{DB_ENGINE}}://{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}?characterEncoding=UTF-8
spring.flyway.password={{DB_PASSWORD}}
spring.flyway.user={{DB_USERNAME}}
spring.flyway.schemas={{DB_NAME}}
spring.flyway.locations=classpath:db/migrations
spring.flyway.out-of-order=true
spring.flyway.baseline-on-migrate=true
```
In the future, the solution will use environment variables instead of replacing them.

## Azure pipeline configuration
I then went to Azure -> Pipelines -> New pipeline -> Starter Pipeline
I have used the Variable Groups in Azure Library to hold most of the configuration
In the `azure-pipelines.yml` file they substitute the fields in the `$(...)` format. In the code, I added a custom task to replace the fields in the `{{...}}` format

```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

            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 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
```

The pipeline build run should look like this:

![Azure pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7qanhuiit9icvx5parc.png)

## Amazon ECR
When the build is successful, it will push a new Docker image to ECR with the `Build.BuildNumber` as the image tag:

![Amazon ECR examples](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9deh8pagqq7yg3n85yj8.png)


This pipeline is set to run after each commit to `main` branch. The images are now ready to be deployed into an environment.

In the next post of the series I will show how I used Amazon Elastic Beanstalk to orchestrate and run the API images.