@rob_rich

Custom Git Deployment to Azure

by Rob Richardson

@rob_rich

https://robrich.org/

About Me

Rob Richardson is a local software craftsman building web properties in ASP.NET and Node, Angular, and React. He’s a Microsoft MVP, published author, frequent speaker at conferences, user groups, and community events, and a diligent teacher and student of high quality software development. You can find this and other talks on https://robrich.org/presentations and follow him on twitter at @rob_rich.

Git deploy options in Azure

Azure Git Deploy Visual Studio Team Services
Free with Azure Web App (including free tier) Separate Azure purchase
Command-line tools and logs Web-based GUI including test result charts
Build
Deploy
Build
Deploy
Track work items
Reporting

Azure Git Deploy

Companion Code: https://github.com/robrich/HelloKudu

Setup

2. Create Web App

source: azure.microsoft.com/en-us/documentation/articles/azure-web-sites-web-hosting-plans-in-depth-overview

3. Enable Git Deploy

source: azure.microsoft.com/en-us/documentation/articles/web-sites-publish-source-control

4. Grab Git URL

source: azure.microsoft.com/en-us/documentation/articles/web-sites-publish-source-control

5. Add azure git remote

From within your git working directory:


git remote add azure https://[email protected]:443/NeedsMoreGit.git
					

6. Send content to Azure


git push azure master
					

Azure Magic Deployments

We pushed code to Azure


git push azure master
					

Results on the command line

source: datascienceandprogramming.azurewebsites.net/wp-content/uploads/2013/11/Capture-132.png

Results in the Azure Portal

source: azure.microsoft.com/en-us/documentation/articles/web-sites-publish-source-control

Results in the Classic Azure Portal

source: erikschlegel.com/2015/06/20/azure-continuous-deployment-using-git-private-repos

We can control the magic

1. Get the Azure CLI Tools

Install node

Install Azure CLI Tools


npm install azure-cli -g
						

2. Generate deployment template (C#)


azure site deploymentscript --aspWAP path/to/Project.csproj -s SolutionFile.sln
					

Docs: github.com/projectkudu/kudu/wiki/Customizing-deployments

2. Generate deployment template (Static Content)

For Node, PHP, ASP.NET Core, etc


azure site deploymentscript --node path/to/deploy/folder
					

Docs: github.com/projectkudu/kudu/wiki/Customizing-deployments

Generated files:

  • .deployment: "where is the deployment script?"
    
    [config]
    command = deploy.cmd
    								
  • deploy.cmd: "the deployment script"

Docs: github.com/projectkudu/kudu/wiki/Customizing-deployments

Deployment script

Deployment script can be:

  • cmd
  • bat
  • bash
  • exe
  • ps1

Powershell is a little weird:


[config]
command = powershell -NoProfile -NoLogo -ExecutionPolicy Unrestricted -Command "& "$pwd\deploy.ps1" 2>&1 | echo"
						

Docs: github.com/projectkudu/kudu/wiki/Customizing-deployments

deploy.cmd


... snip ...

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Deployment
:: ----------

echo Handling .NET Web Application deployment.

:: 1. Restore NuGet packages
IF /I "Hello.sln" NEQ "" (
  call :ExecuteCmd nuget restore "%DEPLOYMENT_SOURCE%\Hello.sln"
  IF !ERRORLEVEL! NEQ 0 goto error
)

:: 2. Build to the temporary path
IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" (
  call :ExecuteCmd "%MSBUILD_PATH%" "%DEPLOYMENT_SOURCE%\src\Web\Web.csproj" /nologo /verbosity:m /t:Build /t:pipelinePreDeployCopyAllFilesToOneFolder /p:_PackageTempDir="%DEPLOYMENT_TEMP%";AutoParameterizationWebConfigConnectionStrings=false;Configuration=Release;UseSharedCompilation=false /p:SolutionDir="%DEPLOYMENT_SOURCE%\.\\" %SCM_BUILD_ARGS%
) ELSE (
  call :ExecuteCmd "%MSBUILD_PATH%" "%DEPLOYMENT_SOURCE%\src\Web\Web.csproj" /nologo /verbosity:m /t:Build /p:AutoParameterizationWebConfigConnectionStrings=false;Configuration=Release;UseSharedCompilation=false /p:SolutionDir="%DEPLOYMENT_SOURCE%\.\\" %SCM_BUILD_ARGS%
)

IF !ERRORLEVEL! NEQ 0 goto error

:: 3. KuduSync
IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" (
  call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_TEMP%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd"
  IF !ERRORLEVEL! NEQ 0 goto error
)

... snip ...
					

3. Commit and push
deployment scripts


git add .deployment
git add deploy.cmd
git commit -m "deployment files"
git push azure master
					

Automate all the things

Run tests

Add more steps to deploy.cmd


:: 2. Build test project
echo Building test project
"%MSBUILD_PATH%" "%DEPLOYMENT_SOURCE%\Tests\Tests.csproj" /property:Configuration=Release
IF !ERRORLEVEL! NEQ 0 goto error

:: 3. Run tests
echo Running tests
"%DEPLOYMENT_SOURCE%\packages\xunit.runner.console.2.1.0\tools\xunit.console.exe" "%DEPLOYMENT_SOURCE%\Tests\bin\Release\Tests.dll"
IF !ERRORLEVEL! NEQ 0 goto error
					

If tests fail, deployment is aborted, current site is unaffected

source: blog.amitapple.com/post/51576689501/testsduringazurewebsitesdeployment

JavaScript tasks

The easiest way is to add npm scripts to package.json


{
  "name": "my-site",
  "version": "1.0.0",

  "scripts": {
    "test": "eslint .",
    "build": "gulp"
  },

  "devDependencies": {
    "eslint": "^2.12.0",
    "gulp": "^3.9.1",
    "gulp-minify-css": "^1.2.4",
    "gulp-uglify": "^1.5.3",
    "mocha": "^2.5.3",
    "chai": "^3.5.0"
  }
}
					

If tasks fail, deployment is aborted, current site is unaffected

JavaScript tasks

Add more steps to deploy.cmd


:: 3. Run ESLint and Gulp
:: upgrade npm
call npm cache clean
call npm install -g npm
pushd "%DEPLOYMENT_SOURCE%"
call npm install && npm test && npm run build
IF !ERRORLEVEL! NEQ 0 goto error
popd
					

If tasks fail, deployment is aborted, current site is unaffected

Set App Version

Add to AssemblyInfo.cs


[assembly: AssemblyInformationalVersion("GITHASH")] // Set to git hash in build file
					

Change GITHASH in deploy.cmd


call :ExecuteCmd PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "(Get-Content Web\Properties\AssemblyInfo.cs).replace('GITHASH', (git rev-parse --short HEAD)) | Set-Content Web\Properties\AssemblyInfo.cs"
					

Read value in C#


Assembly assembly = Assembly.GetExecutingAssembly();
AssemblyInformationalVersionAttribute desc = (
	from a in assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false)
	select a as AssemblyInformationalVersionAttribute
).First();
gitHash = desc.InformationalVersion;
					

Verify Site after Deploy

Add step after deploy in deploy.cmd


:: 9. Test website
:: https://www.amido.com/code/powershell-win32-internal-error-the-handle-is-invalid-0x6/
call :ExecuteCmd PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; exit ((invoke-webrequest -method head -uri 'http://%WEBSITE_HOSTNAME%/' -UseBasicParsing).statuscode - 200)"
IF !ERRORLEVEL! NEQ 0 goto error
					

Database Deployments
with Red Gate

Add more steps to deploy.cmd


:: 6. Migrate database
:: TODO: set %server%, %db%, %user%, %password%
pushd "%DEPLOYMENT_SOURCE%"
call tools\sqlcompare-11.2.3.12\sqlcompare.exe /scripts1:sql /server2:%server% /db2:%db% /username2:%user% /password2:%password% /sync /Include:Identical /exclude:role /exclude:user
IF !ERRORLEVEL! NEQ 0 goto error
call tools\sqldatacompare-11.2.3.12\sqldatacompare.exe /scripts1:sql /server2:%server% /db2:%db% /username2:%user% /password2:%password% /sync /Include:Identical /AbortOnWarnings
IF !ERRORLEVEL! NEQ 0 goto error
:: popd
					

If migrations fail, deployment is aborted, database may be in an unknown state

Automate all the things

User steps to run it:


git push azure