Sunday, April 5, 2020

Basic Azure DevOps Pipeline to deploy an ASP.NET Core Containerized Application and Applying EF Core Database Migrations

In this post, I am going to share a sample azure-pipelines.yml to deploy an ASP.NET Core Containerized Application and Applying Entity Framework Core Database Migrations.

Here the pipeline is based on the following requirement which I believe is common.
  1. When doing the release, we need to apply database migrations in the target database. The migration script will be created on the fly during the execution of the pipeline and will be executed on the target database.
  2. We might need to execute some additional SQL scripts in the target database after the database is migrated, maybe to CREATE/ALTER stored procedures, etc. The pipeline assumes those scripts are residing in scripts/postdeploy folder in the root of the project.
I have left comments in the below azure-pipelines.yml itself, so I don't think any other explanation is necessary. You should be able to changes this as per your needs.

azure-pipelines.yml
trigger:
- master

resources:
repo: self

variables:
  # Container registry service connection established during pipeline creation  
  dockerRegistryServiceConnection: '<dockerRegistryServiceConnection>'

  # Something like xxxxxx.azurecr.io
  containerRegistry: '<containerRegistry>'

  # Docker image name
  imageRepository: '<imageRepository>'

  # The relative localtion of the Dockerfile
  dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'

  # This docker image will be tagged using this
  tag: '$(Build.BuildId)'

  # Agent to be used
  vmImageName: 'ubuntu-latest'

# We have 2 stages, 
#   1. Build
#   2. Release
stages:

# This stage will and build the docker image and push it to ACR
stage: Build
  displayName: Build and push stage
  jobs:
    - job: Build
      displayName: Build
      pool:
        vmImage: $(vmImageName)
      steps:

      - task: Docker@2
        displayName: Build and push an image to container registry
        inputs:
          command: buildAndPush
          repository: $(imageRepository)
          dockerfile: $(dockerfilePath)
          containerRegistry: $(dockerRegistryServiceConnection)
          tags: |
            $(tag)

# This stage will,
#   1. Create a database migration script named update-database.sql inside scripts folder
#   2. Execute the migration script first
#   3. Then execute all the other SQL scripts inside scripts/postdeploy folder
#   4. Finally spin up the container at the target web app
stage: Release  
  displayName: Release stage
  jobs:
    - job: Release
      displayName: Release
      pool:
        vmImage: $(vmImageName)
      steps:

      # This task is required to run dotnet-ef which is getting installed in the next task
      - task: UseDotNet@2
        inputs:
          version: '3.1.200'

      # Install dotnet-ef
      - task: DotNetCoreCLI@2
        displayName: Install dotnet-ef
        inputs:
          command: 'custom'
          custom: 'tool'
          arguments: 'install --global dotnet-ef --ignore-failed-sources'

      # Generate update-database.sql using dotnet-ef
      - task: PowerShell@2
        displayName: Generate Database Migration Script
        inputs:
          targetType: 'inline'
          script: |
            # Splitted into multiple lines for brevity. Below command needs to be a single line
            dotnet ef migrations script 
              -i 
              -o "$(Build.SourcesDirectory)/scripts/update-database.sql" 
              --project "$(Build.SourcesDirectory)/<ProjectThatContainsTheDbContext.csproj>" 
              --startup-project "$(Build.SourcesDirectory)/<TheStartupProject.csproj>"

    # Install SqlServer module to be able to run Invoke-SqlCmd in the next task
      - task: PowerShell@2
        displayName: PowerShell Install-Module SqlServer
        inputs:
          targetType: 'inline'
          script: 'Install-Module -Name SqlServer -AllowPrerelease -Force -Verbose -Scope CurrentUser'

    # Execute the update-database.sql in the target database
      - task: PowerShell@2
        displayName: PowerShell Invoke-Sqlcmd Database Migration Script
        inputs:
          targetType: 'inline'
          script: |
            # Splitted into multiple lines for brevity. Below command needs to be a single line
            Invoke-Sqlcmd 
              -ServerInstance "<ServerInstance>" 
              -Database "<Database>" 
              -Username "<Username>" 
              -Password "<Password>" 
              -Inputfile "$(Build.SourcesDirectory)/scripts/update-database.sql" 
              -Verbose 
              -ConnectionTimeout 120

      # Execute other SQL scripts in the target database
      - task: PowerShell@2
        displayName: PowerShell Invoke-Sqlcmd Other SQL Scripts
        inputs:
          targetType: 'inline'
          script: |
            $files = Get-ChildItem $(Build.SourcesDirectory)/scripts/postdeploy
            foreach ($f in $files) 
            {
              # Splitted into multiple lines for brevity. Below command needs to be a single line
              Invoke-Sqlcmd 
                -ServerInstance "<ServerInstance>" 
                -Database "<Database>" 
                -Username "<Username>" 
                -Password "<Password>" 
                -Inputfile "$f" 
                -Verbose 
                -ConnectionTimeout 120
            }

      # All good, let's spin up a new container at the target web app
      - task: AzureWebAppContainer@1
        displayName: Spin up the container
        inputs:
          azureSubscription: '<azureSubscription>'
          appName: '<appName>'
          containers: $(containerRegistry)/$(imageRepository):$(tag)

          # Something like dotnet TheStartupProject.dll
          containerCommand: '<containerCommand>'
Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment