Define Hooks

Each plan can specify lifecycle event handlers, or hooks, to perform certain actions during a service's runtime. Each hook is a Powershell Core script.

The reference documentation contains the complete list of application lifecycle hooks; however, the only required hook is the run hook, and if you are only calling a built binary, you can specify that through a pkg_svc_run setting. Anything more complicated than running a binary and pointing to a local configuration file in the config directory will require you to create a custom run hook.

Install Hook

There are some dependencies in Windows that present unique challenges when trying to build a fully portable and isolatable package. The .NET framework and services like IIS are good examples of such dependencies. These represent monolithic, single tenant installations that cannot simply be downloaded to the Chef Habitat pkgs folder and run in an isolated fashion. These might require us to run an installer or access a Windows API to propperly install them so that they can play nice with Windows subsystems.

Especially if the installation is required for a package not intended to run as a top level service but is instead designed to be a dependency of a top level service (like a library, runtime, or Windows Feature), we need to place its installation behavior in an install hook. A good example of such a package is the iis-aspnet4 package. This package is not meant to be run as a service inside of a running Supervisor. Rather it is intended to be declared as a dependency of a service which runs as an ASP.NET 4 web application inside of IIS. The iis-aspnet4 install hook will simply enable the appropriate Windows Features required for ASP.NET 4 applications to run in IIS.

When a Chef Habitat Supervisor first loads a service, it will invoke all install hooks included in all direct and transitive dependencies. It will run these hooks from the lowest level dependencies on up and then run the install hook of the top level service, if included, last.

Create an install.ps1 file in your plan's hooks directory and place the following code in that file. Our ASP.NET install hook will install the xWebAdministration Powershell DSC module and will also make sure that the Nuget package provider is installed since that is required for the Install-Module call.

# We need to install the xWebAdministration DSC resource.
# Habitat runs its hooks inside of Powershell Core but DSC
# configurations are applied in a hosted WMI process by
# Windows Powershell. In order for Windows Powershell to locate
# the installed resource, it must be installed using Windows
# Powershell instead of Powershell Core. We can use Invoke-Command
# and point to localhost to "remote" from Powershell Core to
# Windows Powershell.
Invoke-Command -ComputerName localhost -EnableNetworkAccess {
$ProgressPreference="SilentlyContinue"
Write-Host "Checking for nuget package provider..."
if(!(Get-PackageProvider -Name nuget -ErrorAction SilentlyContinue -ListAvailable)) {
Write-Host "Installing Nuget provider..."
Install-PackageProvider -Name NuGet -Force | Out-Null
}
Write-Host "Checking for xWebAdministration PS module..."
if(!(Get-Module xWebAdministration -ListAvailable)) {
Write-Host "Installing xWebAdministration PS Module..."
Install-Module xWebAdministration -Force | Out-Null
}
}

An install hook will only be run once for a given package or until it completes succesfully (exits 0). Chef Habitat will not invoke the install hook everytime the package is loaded.

Init Hook

Create an init.ps1 file in your plan's hooks directory and place the following code in that file. Our ASP.NET init hook creates a link between the svc/var directory where the Supervisor will run our application and the www directory in the package path of our application where its runtime artifacts are located.

Set-Location {{pkg.svc_path}}
if(Test-Path var) { Remove-Item var -Recurse -Force }
New-Item -Name var -ItemType Junction -target "{{pkg.path}}/www" | Out-Null

Run Hook

Create a run.ps1 file in your plan's hooks directory and place the following code in that file. There is a lot to digest here so have a good look at the comments.

# The Powershell Progress stream can sometimes interfere
# with the Supervisor output. Its non critical so turn it off
$ProgressPreference="SilentlyContinue"
# Leverage the Powershell Module in the dsc-core package
# that makes applying DSC configurations in Powershell
# Core simple.
Start-DscCore (Join-Path {{pkg.svc_config_path}} website.ps1) NewWebsite
# The run hook must run indefinitely or else the Supervisor
# will think the service has terminated and will loop
# trying to restart it. The above DSC apply starts our
# application in IIS. We will continuously poll our app
# and cleanly shut down only if the application stops
# responding or if the Chef Habitat service is stopped or
# unloaded.
try {
Write-Host "{{pkg.name}} is running"
$running = $true
while($running) {
Start-Sleep -Seconds 1
$resp = Invoke-WebRequest "http://localhost:{{cfg.port}}/{{cfg.app_name}}" -Method Head
if($resp.StatusCode -ne 200) { $running = $false }
}
}
catch {
Write-Host "{{pkg.name}} HEAD check failed"
}
finally {
# Add any cleanup here which will run after supervisor stops the service
Write-Host "{{pkg.name}} is stoping..."
."$env:SystemRoot\System32\inetsrv\appcmd.exe" stop apppool "{{cfg.app_pool}}"
."$env:SystemRoot\System32\inetsrv\appcmd.exe" stop site "{{cfg.site_name}}"
Write-Host "{{pkg.name}} has stopped"
}

This applies our DSC configuration which should complete with IIS running our application. It also sets necessary permissions on our config file to ensure the IIS app pool user can access it. The hook then spins endlessly in a loop as long as the application is responsive or until it is explicitly shut down via a stop or unload command.

Next: Build and Test