WordPress and OpsWorks, “Pride and Prejudice” (Part 4)

4th part of the serie titled “Wordpress and OpsWorks“ (here you can find the 1st, 2nd and 3rd part).

Despite the title, this time I’m not going to talk about an aspect of OpsWorks specifically related to WordPress, since I’ll be presenting a brute force way to modify the default attributes merging behavior of OpsWorks.

Recently I faced a problem with the cookbooks and the recipes I’d written to handle the deploy of specific PHP applications (WordPress and PrestaShop). I needed to duplicate the information stored inside the default attributes from the coobooks to the custom JSONs (the one related to the stack as well as the one specifiable at deploy time).

My main problem was with the shared assets, i.e. the files and
directories that should be preserved across the deployments such as for example the .htacces or the upload
directory in a WordPress application.

These assets are generally handled by creating proper symlinks pointing to a location external to the application root. In OpsWorks this is the shared directory, located at the same level as the current directory where the application is deployed.

In my cookbooks I stored the information needed to create the symlinks to the shared assets dir inside the default attributes. The problem was that whenever I specified additional shared assets inside the custom JSONs, those were completly overriding the default ones. In order to fix this I needed to specify all the shared assets inside the JSONs, not only the additional but also the default ones.

I found out that this was due to how OpsWorks handles the merge of all the attributes during execution of the recipes.

Then it occurred to me that the way to solve the problem was to explicitely merge the attributes in the order I needed. To do this I relied on the Chef deep_merge implementation, Chef::Mixin::DeepMerge.deep_merge().

Here I’ve extraced the code with the set up of the shared assets default attributes and the subsequent merging with the custom ones:

  ...
  default_deploy = deploy.dup
  custom_deploy = node.fetch('custom_deploy', {}).fetch(application, {}).dup

  default_attributes = {
    ...
    'shared_assets' => [
      { 'type' => 'file', 'from' => '.htaccess', 'to' => '.htaccess' },
      { 'type' => 'dir', 'from' => 'wp-content/uploads', 'to' => 'uploads' },
      { 'type' => 'dir', 'from' => 'wp-content/updraft', 'to' => 'updraft' },
      { 'type' => 'dir', 'from' => 'wp-content/cache', 'to' => 'cache' },
      { 'type' => 'dir', 'from' => 'wp-content/w3tc-config', 'to' => 'w3tc-config' },
    ],
    ...
  }

  default['deploy'][application] = Chef::Mixin::DeepMerge.deep_merge(
    default_deploy,
    Chef::Mixin::DeepMerge.deep_merge(
      default_attributes,
      custom_deploy
    )
  )

The firsts two dup call shouldn’t really be needed as the Chef deep_merge should already implicitely duplicate the hashes passed to it but, for some reason, it doesn’t work without them. I promise I’ll try to investigate this 😉

As to the last part, what it does is simply to deep merge the default_attributes specified inside the cookbook with the attributes specified inside the custom JSONs under the key custom_deploy. I needed a different key from the classical deploy to bypass the default OpsWorks merging.

The solution I’ve implemented can hardly be considered aligned with Chef’s and OpsWorks’s best practices but for now it does the job: it “dries out” the duplication of configuration and it simplifies the customization of the applications that need to be deployed.

I’m eager to know other opinions and possible solutions so don’t hesitate! Tweet them to me! 😉

Leave a Reply

wpDiscuz