Helix: Patching your Config Patches for Specific Environments

Following a conversation on the #helix-habitat channel over on Sitecore Slack Chat a few days ago, I thought it would be worth penning a quick post…

A question was asked about “How to organise your CM vs CD config in a Helix Solution”. This was about configs in your specific implementation, not the default Sitecore configs that need to be enabled or disabled for a specific Sitecore environment.

For example, you may have some specific custom pipelines or event handlers that should only run on the CM instance.

Some suggested that these custom configs should be enabled/disabled/modified as part of the deployment process using PowerShell, possibly using an XML file which provides a mapping of files for the environments. This could then be maintained by the developers.

I suggested a different approach, and “tagging” your config nodes to make them easier to patch… The approach was new/unknown to some so I thought I would (re)blog it.

Managing Helix Configuration

The Helix documentation states the following:

The key to long-lasting good configuration management is isolating not only the implementation specific changes and additions from the standard Sitecore and .NET configuration, but also keeping any changes and additions to configuration together with the business logic which needs it. The first makes it possible to easily identify changes – making upgrades much easier – and the latter makes it easy to identify the reason for changes – making issue resolution and implementation changes stress-free.

I agree with the above statement. Having to leave the Visual Studio solution, hunt down a deployment script (which may or may not be in Source Control) and then modify it for all builds, even current ones that possible may not have a feature merged back in yet… well it starts to get a bit complicated and disconnected for my liking.

Include Folder Structure

The solution I am currently working on is not based on Helix principles. Nonetheless, the process would work the same given that the folder structure of a Helix solution follows set of conventions and strict structure.

We define our config files in a z.Project folder to ensure they are patched last. We also define a “Role” folder below this, in our instance we only have CM and CD servers (no processing, reporting, publishing etc):

Within your Foundation/Feature/Projects, add a subfolder to your module config which defines overrides to patch for each specific environment. Since everything is just part of your solution, everything is kept nicely and tightly together with your code. Remember, these are solution wide scoped and controlled by the developers.

A Helix based project would be organised like so:

Patch Override Syntax

Normally the patch syntax is a little verbose and places dependency on specific types in multiple config files. The following syntax is a rehash from my previous post about site specific pipelines, which will help with this issue.

The premise is to “tag” your config nodes with something unique and then use that attribute to patch override for your environment specific config.

For example, given the following config:

<pipelines>
  <initialize>
    <processor type="MyProject.CMS.Pipelines.CustomRouting, MyProject.CMS" 
                patch:before="*[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']"
                desc="MyProject.CustomRouting" />
  </initialize>
  <httpRequestBegin>
    <processor type="MyProject.CMS.Pipelines.CustomItemResolver, MyProject.CMS"
                patch:after="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" 
                name="MyProject.CustomItemResolver" />
  </httpRequestBegin>
</pipelines>

<events>
  <event name="item:saved">
    <handler type="MyProject.CMS.Events.CustomItemSavedHandler, MyProject.CMS" 
              method="OnItemSaved" 
              hint="MyProject.CustomItemSavedHandler">
    </handler>
  </event>

We can patch this from the config in our environment override much more simply without having to specify the full type element.

<pipelines>
  <initialize>
    <processor desc="MyProject.CustomRouting">
      <patch:delete />
    </processor>
  </initialize>
  <httpRequestBegin>
    <processor name="MyProject.CustomItemResolver">
      <patch:delete />
    </processor>
  </httpRequestBegin>
</pipelines>

<events>
  <event name="item:saved">
    <handler hint="MyProject.CustomItemSavedHandler">
      <patch:delete />
    </handler>
  </event>
</events>  

If the definition of any of the code ever changes, you only need to edit one config file, not the environment specific configs as well.

Note that our patch delete syntax only uses the additional attribute we specified. There is also a mix of attribute usage here – desc, name and hint. It really doesn’t matter what you use (but beware that in certain elements like list parameters hint has meaning for example).

Yo Dawg

Environment Specific Deploy Step

We use Octopus Deploy but the process should be the same for any continuous deployment tool.

Add a deployment step, and if the current server is CM then delete the WebCD folder. That’s it, the logic is that simple.

Helix? Well remember those conventions and folder structure I mentioned earlier, this is where it makes our life simple again. Habitat uses these same folder conventions in the gulp scripts to publish on build for example. So in a similar manner a script such as this should suffice:

cd C:\inetpub\wwwroot\habitat.dev.local\Website\App_Config\Include

Remove-Item *\ContentDelivery -Recurse -Force -ErrorAction Ignore

The above PowerShell will delete all folders called ContentDelivery located within subfolders of the Include folder, e.g. \Include\Foundation\ContentDelivery, \Include\Feature\ContentDelivery, \Include\Project\ContentDelivery etc

If you have multiple server roles then tweak the script to delete all sub-folders that do not match the current environment.

Other Attribute Use Cases

The use of additional attributes to manage configs is useful in other scenarios as well.

For example, if you want to patch the list of sites in the HtmlCacheClear handler:

<event name="publish:end">
  <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel">
    <sites>
      <site name="habitat">habitat-demo</site>
    </sites>
  </handler>
</event>

The use of the additional attribute will mean the site definition is now unique and appended rather than overwriting existing/default definitions.

The same was also suggested when Kam Figy was trying to patch the jpeg and png pipelines in Dianoga, since without the additional attribute each patch would just overwrite the previous.

As mentioned, this article is a rehash from my previous post about site specific pipelines. The same still applies here for site-specific implementations. For example, if you have pipelines in Foundation or Feature that needs to be opt-in, then specific Projects can patch into those using the attribute tag. Since Foundation is not aware of Projects, it cannot add them in to start with, but Projects can reference and be aware of Foundation so this does not break Helix principles. Again, the same benefit applies: the processor is only defined once and if the implementation/type is changed then it only needs to be done in a single place.

Alternative Facts

As always, have a better solution, a different solution or think there is a flaw in this then get in touch – I would be happy to see how others have approached this same issue.

Advertisements

5 comments

  1. https://github.com/efocus-nl/efocus.sitecore.conditionalconfigs FTW 🙂
    Latest version supports any environment variable, so you could set ‘machineRole=CD’ on your frontend for example (using azure you can just set that in the application-settings)

    • jammykam · May 3

      Nice, had not seen this before. Have you seen this from Sitecore that does similar? https://github.com/Sitecore/Sitecore-Configuration-Roles

      I generally tend to steer clear of modules when possible if I can get away with doing something using standard functionality. I only have a small brain so less to remember :p

      • gorhal · 21 Days Ago

        Thanks for the great tip regarding Sitecore-Configuration-Roles, this is very very cool 🙂

  2. Pingback: Handling old files with gulp in the Sitecore Helix webroot – Sitecore Shack
  3. Pingback: Continuous integration and deployment of Sitecore Habitat/Helix as an Azure Web App | Visions In Code

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s