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.