Alexander Beletsky's development blog

My profession is engineering

Continuous Delivery: Setup and run

In my previous post I tried to make it clear that continuous production is for good. This post I’ll show how to prepare environment for continuous production. The idea is that you should be able to configure and test it locally. All configurations have to be part of source code under SCM. It should not depend on machine and run any environment you like. Success criteria is: pick up clean machine, do checkout, run build.bat/deploy.bat and have installed web application.

Integration and database deployment

As I said, UppercuT and RoundhousE are really nice tools for that. As soon as you follow the instruction’s you will have a build.bat, that would be able to build up all binaries, run tests against that and put all build artifacts to package. That is pretty good for start, but we still missing “deployment” part.

As you a little bit more familiar with UppercuT, it provides good facilities for deployment as well. Basically, there are folder deployment\templates\ where you able to define your custom deployment scripts. Typical web application requires 2 scripts:

  • AppDeployment.bat - for web site deployment
  • DbDeployment.bat - for database deployment

There files are templates, from which script for particular environment is generated. The environment is defined in settings folder and include such information as deploy folder, web site name, database name, server name as number of variables. Example,

<?xml version="1.0" encoding="utf-8" ?>
<project name="Settings">
  <!-- environment settings -->
  <property name="environment" value="PRODUCTION" />
  <!-- servers -->
  <property name="server.database" value=".\SQLEXPRESS" />
  <property name="web.deploy.folder" value="c:\trackyt.net\web\" />
  <property name="web.site.name" value="trackyt.net" />

  <property name="database.name" value="trackytdb" />
  <property name="log.level" value="DEBUG" />
  <property name="app.user.name" value="alexander.beletsky" />

  <!-- base settings -->
  <property name="project.name" value="trackyt.net" overwrite="false" />
  <property name="repository.path" value="git://github.com/alexbeletsky/trackyt.net" />
  <property name="folder.app.drop" value="${project.name}" overwrite="false" />
  <property name="folder.database" value="db" overwrite="false" />

  <!-- database deployment -->
  <property name="dirs.db" value="..\${folder.database}" />
  <property name="file.version" value="_BuildInfo.xml" overwrite="false" />
  <property name="restore.from.path" value="..\${database.name}.bak" overwrite="false" />

</project>

In template .bat file it is possible to refer, to some particular variable, so it is possible to make those quite generic. After build, template .bat files are post-processed and actual batch is generated. The name would be like ENVIRONMENT.AppDeployment.bat, where ENVIRONMENT is type of environment you defined.

Web site deployment script

If you do ASP.net (MVC) website in 99.9% cases you will be happy with simple XCOPY deployment type. Basically it means, simple copy of website to defined IIS folder.

But, as soon it is continuous production deploy it means that Web Site is already running. It would not be possible to re-write some files, since they could be used by IIS. So, we need to stop the site before update. I found very good possibility for that with %windir%\system32\inetsrv\appcmd command. After site is stopped, we just copy full content of Web folder, remove some redundant files and run site again. In batch code it would look like,

@echo off

SET DIR=%~d0%~p0%

SET web.deploy.folder="${web.deploy.folder}"

echo stopping web site..
call %windir%\system32\inetsrv\appcmd stop site ${web.site.name}
if %ERRORLEVEL% NEQ 0 goto errors

echo copy application content
rmdir /s /q %web.deploy.folder%
xcopy /E /F /H /R ..\_PublishedWebSites\Web %web.deploy.folder%
xcopy ..\build_artifacts\_BuildInfo.xml %web.deploy.folder%
if %ERRORLEVEL% NEQ 0 goto errors

echo remove redudant files
del %web.deploy.folder%*Tests*.htm*
del %web.deploy.folder%Web.Debug.config
del %web.deploy.folder%Web.Release.config
del %web.deploy.folder%*packages*
if %ERRORLEVEL% NEQ 0 goto errors

echo starting web site
%windir%\system32\inetsrv\appcmd start site ${web.site.name}
if %ERRORLEVEL% NEQ 0 goto errors

goto finish

:errors
EXIT /B %ERRORLEVEL%

:finish

Database deployment script

RoundhousE does all infrastructure work for us. All we need to create a batch file, that would be able to run during continuous production cycle. As well as AppDeployment.bat I defined DbDeployment.bat in deployment\templates\ folder. But before any update of database it is always good to have a backup, to be able to restore from it if something went wrong. Actually, RoundhousE should have such ability, but unfortunately I didn’t get how to use it. I’ve created my simple SQL script that is able to backup.

USE $(Database);
GO
BACKUP DATABASE $(Database)
TO DISK = 'C:\backup\$(Database).bak'
   WITH FORMAT,
      MEDIANAME = 'C_SQLServerBackups',
      NAME = 'Full Backup of $(Database)';
GO

And corresponding batch file, that would run backup.sql.

@echo off

if '%1' == '' goto usage
if '%2' == '' goto usage

sqlcmd -S %1 -i .\scripts\backupdb.sql -v Database = %2 -e
if %ERRORLEVEL% NEQ 0 goto errors

goto finish

:usage
echo.
echo Usage: backup.bat [server] [database]
echo [server] - server eg. mymachine\SQLEXPRESS
echo [database] - name of database to backup
echo.
EXIT /B 1

:errors
EXIT /B %ERRORLEVEL%

:finish

Both files are placed into deployment\scripts folder. So, the DbDeployment.bat template, would first run database backup and if it is successfull, run RoundhousE to update database.

@echo off

SET database.name="${database.name}"
SET sql.files.directory="${dirs.db}"
SET server.database="${server.database}"
SET repository.path="${repository.path}"
SET version.file="${file.version}"
SET version.xpath="//buildInfo/version"
SET environment="${environment}"

echo backup database
call .\scripts\backupdb.bat %server.database% %database.name%
if %ERRORLEVEL% NEQ 0 goto errors

echo update database
"%DIR%rh\rh.exe" /d=%database.name% /f=%sql.files.directory% /s=%server.database% /vf=%version.file% /vx=%version.xpath% /r=%repository.path% /env=%environment% --ni --simple
if %ERRORLEVEL% NEQ 0 goto errors

goto finish

:errors
EXIT /B %ERRORLEVEL%

:finish

Putting it all together

We already have build.bat as part of UppercuT, now we need to define deploy.bat that would do deployment of product. It would be called immediately after build.bat finished, so binaries are ready, tests passed and code_drop folder contains all artifacts for deployment. The script is rather simple and utilize stuff we did previously.

@echo off

if '%1' == '' goto usage

SET ENV=%1

cd .\code_drop\deployment

echo Deploy database
call .\%ENV%.DbDeployment.bat
if %ERRORLEVEL% NEQ 0 goto errors

echo Deploy application
call .\%ENV%.AppDeployment.bat
if %ERRORLEVEL% NEQ 0 goto errors

goto finish

:usage
echo.
echo tracky.net deploy script
echo Usage: deploy.bat [environment]
echo [environment] - deployment environment could be STAGING or PRODUCTION
echo.
EXIT /B 1

:errors
echo Build FAILED
EXIT /B %ERRORLEVEL%

:finish
echo Build SUCCESS

Notice, it receives the parameter ENV. It will contain type of environment for deployment. For staging environment, you should call deploy.bar STAGING, for production deploy.bat PRODUCTION.

Testing it out

That’s basically it. Now, you should make sure everything works as expected. Run build.bat/deploy.bat, make sure build went with no errors, deploy.bat correctly does back up of database, updates database and update site content.

As I said on top, it is very important that configuration is part of product, part of source code. If you follow this, it will be possible to deploy application but just getting sources from SCM. This is first step of setting up your Continuous Production server.