There are many articles written on how to create ‘perfect’ continues deployment process with Sitecore, but in my 7 years of Sitecore experience I did not had opportunity to see many functional and complete implementations. The file deployment was never an issue, but synchronous deployment of content(items) and files was.
The main issue that make continues deployment difficult to implement in Sitecore is complexity around deploying items and files in order. I’ve seen different strategies, including deploying with Chief, IIS Web Deploy with Teamcity and TDS deploy, Octopus deployment and many other approaches. Most approached I have seen came short providing full spectrum of deployment features needed for enterprise, but mainly ability to deploy with one button click and roll back if deployment failed.
I was fortunate enough to have opportunity to work on continues integration prototype solutions. In this article, I’ll discuss continues deployment of TDS packages.
To have continuous deployment of Sitecore items we opted to use TDS and UpdateInstallationWizard interface provided by Sitecore.
The process is very simple. We used msbuild to compile and build TDS packages, the additional script used to collect packages or package, in our case we used multiple project structure, where each project represented a site or feature, and sequentially deploy each package at a time through custom Service.
The service is designed to import TDS package and log the activity.
[WebMethod(Description = "Installs a Sitecore Update Package.")] public void InstallUpdatePackage(string path) { // Use custom logger var log = Sitecore.Diagnostics.LoggerFactory.GetLogger("PackageLogFileAppender"); var file = new FileInfo(path); if (!file.Exists) throw new ApplicationException(string.Format("Cannot access path '{0}'.", path)); log.Info(path); using (new SecurityDisabler()) { var installer = new DiffInstaller(UpgradeAction.Upgrade); var view = UpdateHelper.LoadMetadata(path); //Get the package entries bool hasPostAction; string historyPath; var entries = installer.InstallPackage(path, InstallMode.Install, log, out hasPostAction, out historyPath); installer.ExecutePostInstallationInstructions(path, historyPath, InstallMode.Install, view, log, ref entries); } }
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <log4net> <appender name="InstallerLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging"> <file value="$(dataFolder)/logs/AutonationIntaller.{date}.txt" /> <appendToFile value="true" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n" /> </layout> <encoding value="utf-8" /> </appender> <logger name="InstallerLogFileAppender" additivity="false"> <level value="INFO" /> <appender-ref ref="InstallerLogFileAppender" /> </logger> </log4net> </sitecore> </configuration>
Now, we were able to install a package programatically, but one may ask: how would you install more than one package sequentially, without knowing that previous package was deployed successfully, especially if solution is based on multi project solution (like, Habitat). For this prototype solution, we have created custom log processor. This script makes HTTP call to instantiate install of package and will wait until package installation is complete by checking logs.
#package and push the path contents to OD function Install-TDS { param( [string]$path ) Write-Host "---$path---" foreach($package in (Get-ChildItem -Path $path)) { $packageName = $package.Name Write-Host "Installing $packageName" $postParams = @{path="$path\$packageName"} $retry = $webRetryCount while($retry -gt 0) { try { Write-Host "Making POST Request: $postParams" Invoke-WebRequest -Uri $url -Method POST -Body $postParams -Headers $headers -TimeoutSec $timeout $retry = 0 Write-Host "Attempt: $retry" } catch { Write-Host $_.Exception.Message $retry = $retry - 1 if($retry -eq 0) { throw $_.Exception.Message } } } $date = Get-Date $date1 = $date | Get-Date -Format "M/d/yyyy" $date2 = $date | Get-Date -Format "-h:mm tt" $date2length = $date2.Length $success = -1 $sw = [system.diagnostics.stopwatch]::startNew() while($sw.Elapsed.Seconds -le $timeout) { $installerName = (Get-ChildItem -Path $datafolder\* -Include *AutonationIntaller* | sort LastWriteTime | select -last 1).Name Write-Host "Log File Name: $installerName for $packageName" $fileContent = Get-Content $datafolder\$installerName -Raw $posmatch = $fileContent -cmatch "(?sm)$packageName((?! ERROR ).)*$date1.{$date2length}: TDS PostDeployActions complete" if($posmatch) { #break out of loop the process is done Write-Host "Installed Package: $packageName" $success = 1 break } $negmatch = $fileContent -cmatch "(?sm)$packageName.* ERROR .*$date1.{$date2length}: TDS PostDeployActions complete" if($negmatch) { Write-Host "Error Package: $packageName" #if there was en error throw break $success = 0 break } Start-Sleep -s 3 } $sw.Stop() if($success -lt 1) { if($success -eq -1) { Write-Host "Package Timeout: $packageName" } throw [System.Exception] "$packageName failed to install." } } }
This approach was created as a prototype and would require addition work to make it ‘production-ready’. For example, I would like to avoid checking logs for installation completion and have installer method to return true or false, but it is not organically possible now without extending “installer.ExecutePostInstallationInstructions” class.
I would love to hear if anyone have successfully extended installer.ExecutePostInstallationInstructions to return installation status or made it in any other way.
Recent Comments