Cloud Foundry buildpacks offer a convenient way to bootstrap applications. At its core, it’s a code recipe that prepares the application to be launched on Cloud Foundry by doing things like injecting dependencies (runtime, middleware, etc), setting environmental variables, modifying application config parameters. If you’re a Cloud Foundry user, you probably have many buildpacks already available to you, which you can list with cf buildpacks
command. These are the ones that are installed on the platform and are available to use out of the box. However, unless disabled by the platform operator, you can also specify buildpacks off the platform just by pointing to the buildpack package URL. On the one input, we have the original artifact that the developer gave the platform via cf push
command, and the output of is tarball that contains the application and all the dependencies introduced by the buildpack, being similar in nature to a simplified docker container. Buildpacks can also be chained, essentially acting as steps in a pipeline of preparing your app to be run on the platform. The final buildpack in the chain is special as it determines the startup command for the application, while any intermediatory buildpacks are referred to as “supply buildpacks”.
So what exactly is in a buildpack and how does it work? Buildpack includes shell hooks that Cloud Foundry will invoke at different stages of the application lifecycle, which are as follows:
bin/detect
determines whether or not to apply the buildpack to an app.bin/supply
provides dependencies for an app.bin/finalize
prepares the app for launch.bin/release
provides feedback metadata to Cloud Foundry indicating how the app should be executed.
Each hook will be called with a set of parameters, and expected to either return code to the platform or write it’s output to standard out. The actual implementation of each hook can be any valid executable on the platform, from shell script to compiled binary. More details on the buildpack contract can be found on the official Cloud Foundry buildpack documentation. Buildpacks are packaged up as zip file and be either installed on the platform by platform operator via cf create-buildpack
command or referenced in the manifest by its published URL.
The good news is I’ve created a buildpack template in .NET, allowing you to build a new buildpack just by implementing a few abstract methods. The provided build script will build and package your project into the expected buildpack format so that you can use directly in your manifest – and it works on both Linux and Windows stacks!
Let’s see how easy it is to actually do this. Today I’m going to show you how to build a buildpack that will replace any values for configuration\appsettings
inside web.config with matching values provided in environmental variables.
First, we going to create a new project via buildpack template:
dotnet new --install CloudFoundry.Buildpack.V2 dotnet new buildpack -n TransformBuildpack
Open up the solution created and look at TransformBuildpack class (name of the main class will match the name of your project).
public class TransformBuildpack : SupplyBuildpack { protected override bool Detect(string buildPath) { return false; } protected override void Apply(string buildPath, string cachePath, string depsPath, int index) { Console.WriteLine("=== Applying TransformBuildpack ==="); } }
Depending on the type of buildpack you plan on building, inherit either from SupplyBuildpack
or FinalBuildpack
. The Detect
method can be used to allow buildpack to automatically determine if it should be applied by examining the content of the application located at `buildPath` parameter. Detection will only run if the buildpack is installed on the platform. If it is explicitly specified in the manifest, it will be applied regardless of what is in the Detect method.
The detection phase for this is very simple – we just want to check if there’s a web.config
in the application folder. We can implement that with a single line like this:
protected override bool Detect(string buildPath) => File.Exists(Path.Combine(buildPath, "web.config"));
The meat of the buildpack happens in the Apply
method. Depending on whether you inherit from SupplyBuildpack
or FinalBuildpack
, this method will be invoked either on /bin/supply
or /bin/finalize
hook. The parameters are as following:
buildPath
– Directory path to the applicationcachePath
– Location the buildpack can use to store assets during the build processdepsPath
– Directory where dependencies provided by all buildpacks are installed. New dependencies introduced by current buildpack should be stored inside subfolder named with index argument ({depsPath}/{index})index
– Number that represents the ordinal position of the buildpack
We want to open up web.config
if it exists, read appSettings block and for every value check if there’s a matching environmental variable that will override the default setting. The implementation would look as follows:
protected override void Apply(string buildPath, string cachePath, string depsPath, int index) { var webConfig = Path.Combine(buildPath, "web.config"); if (!File.Exists(webConfig)) { Console.WriteLine("Web.config not detected"); Environment.Exit(0); } var doc = new XmlDocument(); doc.Load(webConfig); var adds = doc.SelectNodes("/configuration/appSettings/add").OfType<XmlElement>(); foreach(var add in adds) { var key = add.GetAttribute("key"); if(key == null) continue; var envVal = Environment.GetEnvironmentVariable(key); if(envVal != null) add.SetAttribute("value", envVal); } doc.Save(webConfig); }
Now we just need to compile this into a package that Cloud Foundry understand. The template makes this super easy via the included build script (powered by Cake, see cake.build
file for details). The build scripts accepts a stack
parameter depending on which Cloud Foundry stack you’re targeting – in this case we want Windows. (Note, you can’t compile Windows buildpack from Linux or Mac because it requires .NET Framework – for Linux the buildpack will be compiled to use self contained .NET core)
.\build.ps1 -ScriptArgs '-stack=windows'
.\build.ps1 -ScriptArgs '-stack=linux'
./build.sh --stack=linux
You should find the output in the /publish
directory. Simply publish it to a public URL (I recommend GitHub releases page if you’re hosting your buildpack on GitHub), and reference in your manifest
--- applications: - name: my-config random-route: true memory: 512M stack: windows2016 buildpacks: - https://github.com/macsux/cf-buildpack-template/releases/download/1.0/TransformBuildpack-win-x64-1.0.609536966.zip - hwc_buildpack env: MyKey: Hello
<configuration> ... <appSettings> <add key="MyKey" value="default"/> </appSettings> ... </configuration>
<configuration> ... <appSettings> <add key="MyKey" value="Hello"/> </appSettings> ... </configuration>
Note that the buildpack format is changing from V2 to V3 which will introduce a lot of extra flexibility; however, the mechanics of the contract will change significantly. At this time of writing, V3 buildpack is not supported on Windows. The buildpack template used is targeting V2 format.