20
Oct

setting up azure web site deployment from github

This feature of Azure is pretty killer, but like most things... the simple hello world example only goes so far in the real world. I'm writting up my experience of peeling back the curtain and getting this working in my real world use case.

I'm not sure what convention of the default intelligence I violated to render the stock behavior boken, but I think it might be when I introduced inner-solution libraries that my web application was dependent on. At any rate I was seeing failed deployments on my Azure web site that suggested nuget dependencies were not being pulled down properly. I needed to understand what was going on behind the scenes on the Azure side because everything was working for me locally.

My error message was this:

Command: "D:\home\site\deployments\tools\deploy.cmd"

Handling .NET Web Application deployment.

All packages listed in packages.config are already installed.

D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]

D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil.Mdb". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]

D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil.Pdb". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]

D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil.Rocks". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]

Services\ClassDiagramCodeValidator.cs(4,7): error CS0246: The type or namespace name 'Mono' could not be found (are you missing a using directive or an assembly reference?) [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]

Services\ClassDiagramCodeValidator.cs(5,7): error CS0246: The type or namespace name 'Mono' could not be found (are you missing a using directive or an assembly reference?) [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]

Failed exitCode=1, command="D:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" "D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj" /nologo /verbosity:m /t:Build /t:pipelinePreDeployCopyAllFilesToOneFolder /p:_PackageTempDir="C:\DWASFiles\Sites\~1nplant\Temp\25f04cfa-d0d4-4011-bf68-1eafcd42f44e";AutoParameterizationWebConfigConnectionStrings=false;Configuration=Release /p:SolutionDir="D:\home\site\repository\.\\"

An error has occurred during web site deployment.

Handling .NET Web Application deployment.  
All packages listed in packages.config are already installed.  
D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]  
D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil.Mdb". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]  
D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil.Pdb". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]  
D:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Mono.Cecil.Rocks". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]  
Services\ClassDiagramCodeValidator.cs(4,7): error CS0246: The type or namespace name 'Mono' could not be found (are you missing a using directive or an assembly reference?) [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]  
Services\ClassDiagramCodeValidator.cs(5,7): error CS0246: The type or namespace name 'Mono' could not be found (are you missing a using directive or an assembly reference?) [D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj]  
Failed exitCode=1, command="D:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" "D:\home\site\repository\Src\NPlant.Web\NPlant.Web.csproj" /nologo /verbosity:m /t:Build /t:pipelinePreDeployCopyAllFilesToOneFolder /p:_PackageTempDir="C:\DWASFiles\Sites\~1nplant\Temp\25f04cfa-d0d4-4011-bf68-1eafcd42f44e";AutoParameterizationWebConfigConnectionStrings=false;Configuration=Release /p:SolutionDir="D:\home\site\repository\.\\"  
An error has occurred during web site deployment.  
D:\Program Files (x86)\SiteExtensions\Kudu\28.31006.1184\bin\scripts\starter.cmd "D:\home\site\deployments\tools\deploy.cmd"  

Intro to Source Control Deployments

Azure has the ability to tie an Azure Web Site to a source control repository. When you setup the Web Site, it will give you an option to associate a source control repo:

Note the "Publish from source control" checkbox. When you check this you'll be presented with a nice list of well known, pre-integrated source control providers:

I'm using github in this scenario. This will setup hooks on the github side to talk to Azure when commits occur and Azure will subsequently kick off deployments. Right there is one of the gray areas - where's the build? You'll never see the "build" part in the hello world demos, but that's handled by an engine called Kudu that's open source and hosted out on github here: Project Kudu

We'll get into the details of Kudu in a second, but first have a watch of this video to understand the high level capabilities of source control deployments for Web Sites: Deploying to Web Sites with GitHub using Kudu - with David Ebbo

Troubleshooting Kudu

One clue in how to peek behind the curtain was the first line in the error message provided above. Here it is again:

Command: "D:\home\site\deployments\tools\deploy.cmd"  

OK, what is deploy.cmd? I didn't write it. Who did and how to I get a look at what it's doing?

The relevant part of the Kudu wiki for this is here: Deployment Hooks. In a nut shell Kudu is trying to be intelligent by looking at your repo and the files in it and applies to conventions in determining how to build your app. Those conventions are outlined here: How Kudu Works and Customizing Deployments.

It turns out Kudu is generating that deploy.cmd file on the fly. It is applying the above mentioned conventions and in turn creating this script. The good news is you can use the Azure CLI to generate this same script. This is covered in the "Deployment Script Generator" section of the Deployment Hooks page.

Here's what I did to generate the deploy.cmd file locally:

  • Ensure you have the Azure CLI installed: Azure Downloads
  • Open a command prompt in your app's root directory. For me that is C:\github\nplant.web.
  • Run the following command (note my solution file is in the root and my web app (WAP) is in a sub folder Src\NPlant.Web):
C:\github\nplant.web>azure site deploymentscript --aspWAP Src\NPlant.Web.csproj -s NPlant.sln  
  • This will generate something like this when successful:

  • You will then see a "deploy.cmd" file generated in the root:
@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off

:: ----------------------
:: KUDU Deployment Script
:: Version: 0.1.11
:: ----------------------

:: Prerequisites
:: -------------

:: Verify node.js installed
where node 2>nul >nul  
IF %ERRORLEVEL% NEQ 0 (  
  echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment.
  goto error
)

:: Setup
:: -----

setlocal enabledelayedexpansion

SET ARTIFACTS=%~dp0%..\artifacts

IF NOT DEFINED DEPLOYMENT_SOURCE (  
  SET DEPLOYMENT_SOURCE=%~dp0%.
)

IF NOT DEFINED DEPLOYMENT_TARGET (  
  SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot
)

IF NOT DEFINED NEXT_MANIFEST_PATH (  
  SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest

  IF NOT DEFINED PREVIOUS_MANIFEST_PATH (
    SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest
  )
)

IF NOT DEFINED KUDU_SYNC_CMD (  
  :: Install kudu sync
  echo Installing Kudu Sync
  call npm install kudusync -g --silent
  IF !ERRORLEVEL! NEQ 0 goto error

  :: Locally just running "kuduSync" would also work
  SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd
)
IF NOT DEFINED DEPLOYMENT_TEMP (  
  SET DEPLOYMENT_TEMP=%temp%\___deployTemp%random%
  SET CLEAN_LOCAL_DEPLOYMENT_TEMP=true
)

IF DEFINED CLEAN_LOCAL_DEPLOYMENT_TEMP (  
  IF EXIST "%DEPLOYMENT_TEMP%" rd /s /q "%DEPLOYMENT_TEMP%"
  mkdir "%DEPLOYMENT_TEMP%"
)

IF NOT DEFINED MSBUILD_PATH (  
  SET MSBUILD_PATH=%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe
)

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

echo Handling .NET Web Application deployment.

:: 1. Restore NuGet packages
IF /I "NPlant.Web.sln" NEQ "" (  
  call :ExecuteCmd nuget restore "%DEPLOYMENT_SOURCE%\NPlant.Web.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\NPlant.Web.csproj" /nologo /verbosity:m /t:Build /t:pipelinePreDeployCopyAllFilesToOneFolder /p:_PackageTempDir="%DEPLOYMENT_TEMP%";AutoParameterizationWebConfigConnectionStrings=false;Configuration=Release /p:SolutionDir="%DEPLOYMENT_SOURCE%\.\\" %SCM_BUILD_ARGS%
) ELSE (
  call :ExecuteCmd "%MSBUILD_PATH%" "%DEPLOYMENT_SOURCE%\Src\NPlant.Web.csproj" /nologo /verbosity:m /t:Build /p:AutoParameterizationWebConfigConnectionStrings=false;Configuration=Release /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
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: Post deployment stub
IF DEFINED POST_DEPLOYMENT_ACTION call "%POST_DEPLOYMENT_ACTION%"  
IF !ERRORLEVEL! NEQ 0 goto error

goto end

:: Execute command routine that will echo out when error
:ExecuteCmd
setlocal  
set _CMD_=%*  
call %_CMD_%  
if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%  
exit /b %ERRORLEVEL%

:error
endlocal  
echo An error has occurred during web site deployment.  
call :exitSetErrorLevel  
call :exitFromFunction 2>nul

:exitSetErrorLevel
exit /b 1

:exitFromFunction
()

:end
endlocal  
echo Finished successfully.  

Conclusion

The Kudu wiki gives some good information on how to customize this (follow the links above), but this will give you precise insight into what it's doing out of the gate. As the wiki doc describes, you can take this generated script, tweak it to your needs and then configure Kudu to use your script instead of generating it's own.

It took me a while to figure out what Kudu was doing because as usual, the demos of this stuff never go into details of how it's working. Hope this is helpful to someone else.

comments powered by Disqus