Migrating our "Living the Dream" DevOps demo to GitHub Enterprise

At Black Marble, we have had a long standing Azure DevOps Team Project that we used for end-to-end demos of the principles of DevOps called Living the Dream. This used a legacy codebase, the old Microsoft Fabrikam demo, and showed that can be deployed using modern tools.

As I had no similar demo for GitHub Enterprise, I thought it would be interesting to see how the migration process taking my Azure DevOps implementation over to GitHub would go. This is a good learning exercise as it is the type of problem that many of our enterprise clients will need to do if changing DevOps platform. My key aim was to do the minimum to get the CI/CD process moved from Azure Pipelines to GitHub Action

Moving the source

This was easy, I just imported the Git repo from Azure DevOps

Build

The build for my solution was fairly easy, the project is a pair of Visual Studio solutions, one for the ARM code and the other for the Website code.

The key point was to make sure I passed in the correct parameters to make sure the web site was packaged up using WebDeploy

 1  Build-Solution:
 2    runs-on: windows-latest
 3
 4    steps:
 5    - uses: actions/checkout@v3
 6
 7    - name: Add MSBuild to PATH
 8      uses: microsoft/setup-msbuild@v1.0.2
 9
10    - name: Restore NuGet packages
11      working-directory: ${{env.GITHUB_WORKSPACE}}
12      run: nuget restore ${{env.SOLUTION_FILE_PATH}}
13
14    - name: Build Solution
15      working-directory: ${{env.GITHUB_WORKSPACE}}
16      # Add additional options to the MSBuild command line here (like platform or verbosity level).
17      # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
18      run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:DeployOnBuild=true /p:PublishProfile=${{env.BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}}

Deployment

The majority of the work was required to get the solution deployed i.e.

  • Creating all the required Azure resources using an ARM template
  • Deploying the website via MSDeploy.

In Azure DevOps I had tasks to manage these steps, for GitHub actions, though some actions exist, I also had to write some scripts.

I ended up putting the Actions required for the ARM and Solution deployment in reusable workflows, so I could call the steps at multiple locations on my workflows (for the test deployment and the production deployment) without repeating actions.

ARM

The workflow for the ARM was as follows. Nothing that special, the key points to note are

  • You have to use the Azure Login action, this in effect replaces the Azure Pipelines service connection.
  • I have to use a script to check the Azure resource group exists prior to the deployment. In Azure Pipelines the ARM task will create the resource group if not present, but this is not so with the GitHub Action
  • I inject all my ARM parameters as inline parameters (as opposed to a file), this was just to keep the same pattern as had been used on Azure DevOps
 1name: Reuseable workflow to publish ARM to Azure resource group
 2
 3on:
 4  workflow_call:
 5    inputs:
 6      environment:
 7        required: true
 8        type: string
 9    # all secrets inherited    
10jobs:
11  Integration-ARM:
12    runs-on: windows-latest
13    environment:
14      name: ${{ inputs.environment }}
15    steps:
16    - name: Download ARM Build Artifact
17      uses: actions/download-artifact@v3.0.1
18      id: arm-download
19      with:
20        # Artifact name
21        name: ARM
22        path: ./ARM
23
24    - name: Azure Login
25      uses: Azure/login@v1.4.6
26      with:
27        creds: ${{ secrets.AZURE_CREDENTIALS }}     
28
29    - name: Ensure Azure Resource Group is created
30      run: |
31        $rgexists = az group exists -n ${{ secrets.AzureResourceGroup}}
32        if ($rgexists -eq 'false') {
33          az group create --name ${{ secrets.AzureResourceGroup}} --location ${{ secrets.AZURELOCATION }}
34          write-host "Creating Azure Resource Group"
35        }        
36      shell: pwsh 
37
38    - name: Deploy Azure Resource Manager (ARM) Template
39      uses: Azure/arm-deploy@v1.0.8
40      with:
41        scope: 'resourcegroup'
42        subscriptionId: ${{ secrets.AZURESUBSCRIPTION }}
43        region: ${{ secrets.AZURELOCATION }}
44        resourceGroupName: ${{ secrets.AzureResourceGroup}}
45        template: '.\\ARM\\Templates\\WebSiteSQLDatabase.json' 
46        deploymentMode: 'Incremental'
47        parameters: 'hostingPlanName="${{ secrets.HostingPlanName}}" hostingPlanSku="${{ secrets.hostingPlanSku}}" hostingPlanCapacity="${{ secrets.hostingPlanCapacity}}" webSiteName="${{ secrets.Sitename}}" sqlserverName="${{ secrets.sqlservername}}" sqlServerAdminLogin="${{ secrets.SQLUser}}" sqlServerAdminPassword="${{ secrets.SQLPassword}}" databaseName="${{ secrets.databasename}}" collation="SQL_Latin1_General_CP1_CI_AS" edition="Standard" maxSizeBytes="1073741824" requestedServiceObjectiveName="S0" appInsightsLocation="${{ secrets.AzureLocation}}" VersionTag="1.2.3"" DeploymentDate="2022-10-28"" EnvironmentTag="tag"'

Solution

The MSDeploy was more problematic. It is fair to say this is not a currently fashionable technology. Web deploy has very much moved to the 'copy a zip file' approach. There was no GitHub Action available to run MSDeploy against Azure, so I had to work out the parameters and script it.

Again the key points to note

  • I could not find a GitHub Action that would automatically update a configuration file replacing tokens found with values from environment variables and secrets. There a few that do part of the job, but not it all. This is something I might well write, but for now a script that replaces the tokens did the job - and yes I know it is probably better practice to set these values in the Azure WebApp directly, but as I said I was trying for a like for like replacement.
  • The MSDeploy relies on pulling down the publish profile then calling the MSDeploy EXE which is present on the GitHub Agent. This took a while to get right, but once it was done, it is reliable
 1name: Reuseable workflow to publish web solution to Azure WebApp
 2
 3on:
 4  workflow_call:
 5    inputs:
 6      environment:
 7        required: true
 8        type: string
 9    # all secrets inherited    
10jobs:
11  Integration-ARM:
12    runs-on: windows-latest
13    environment:
14      name: ${{ inputs.environment }}
15    steps:
16    - name: Download Web Deploy Solution Build Artifact
17      uses: actions/download-artifact@v3.0.1
18      with:
19        # Artifact name
20        name: 'Webdeploy-Package'
21        path: ./WebDeploy
22
23    - name: Replace tokens in configuration file
24      run: |
25        $file = ".\WebDeploy\FabrikamFiber.Web.SetParameters.xml"
26        $filecontent = Get-Content -Path $file
27        $filecontent = $filecontent -replace "__Sitename__", "${{secrets.Sitename}}" 
28        $filecontent = $filecontent -replace "__LOCATION__", "${{secrets.LOCATION}}" 
29        $filecontent = $filecontent -replace "__GENERATETESTDATA__", "${{secrets.GENERATETESTDATA}}" 
30        $filecontent = $filecontent -replace "__sqlservername__", "${{secrets.sqlservername}}" 
31        $filecontent = $filecontent -replace "__databasename__", "${{secrets.databasename}}" 
32        $filecontent = $filecontent -replace "__SQLUser__", "${{secrets.SQLUser}}" 
33        $filecontent = $filecontent -replace "__SQLPassword__", "${{secrets.SQLPassword}}"
34        $filecontent | Out-File $file
35        cat $file                  
36      shell: pwsh
37
38    - name: Azure Login
39      uses: Azure/login@v1.4.6
40      with:
41        creds: ${{ secrets.AZURE_CREDENTIALS }}     
42
43    - name: 'Deploy web site with MSDeploy'
44      run: |
45        $publishProfile = az webapp deployment list-publishing-profiles --resource-group ${{ secrets.AzureResourceGroup}} --name ${{ secrets.Sitename }} --query "[?publishMethod=='MSDeploy']" --subscription "${{ secrets.AZURESUBSCRIPTION}}" | convertfrom-json
46        $shortPath = (New-Object -ComObject Scripting.FileSystemObject).GetFolder("./WebDeploy").ShortPath  
47        & "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" -verb:sync -source:package="$shortpath\FabrikamFiber.Web.zip" -setParamFile:"$shortpath\FabrikamFiber.Web.SetParameters.xml" -dest:auto,ComputerName="https://$($publishProfile.msdeploySite).scm.azurewebsites.net/msdeploy.axd?site=$($publishProfile.msdeploySite)",UserName=$($publishProfile.userName),Password=$($publishProfile.userPWD),AuthType='Basic' -verbose -debug -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension        
48      shell: pwsh

Summary

So I now have the core of my 'Living the Dream' demo on GitHub, the is more of course I can do, but it is a good start and has been a good learning experience.

This form of activity is something I would recommend to anyone trying to get their had around the intricacies of GitHub, or any technology new to them. You always learn more I think when trying to do your own project as opposed to just following a lab.