Over a million developers have joined DZone.

Chef Patterns: Part 1 - Modeling Environment-Specific Differences

DZone's Guide to

Chef Patterns: Part 1 - Modeling Environment-Specific Differences

· DevOps Zone ·
Free Resource

Learn how integrating security into DevOps to deliver "DevSecOps" requires changing mindsets, processes and technology.

At work we have the notion of multiple environments. Dev (used by the developers), Staging (used for showcasing stories), QA/Testing (used by testers) and Production (caters to end user). At times, a pre-production environment is also present to simulate production like scenarios.

Though we fight hard to minimize the differences between these environments, they do exist and it is necessary to measure them, which in turn can provide us with some automated mechanisms to easily spot environment-specific differences. In this write up I'll be writing some of the techniques I'm using to model them. There are two generic goals for this techniques:

  • Testability of the recipe.
  • Ease with which one can spot the environment-specific differences

The techniques:

  1. Conditionals:  Use raw if else conditions to apply different resources in different environments. If you are sure that the difference will only be limited to certain minute details I will start employing raw conditional  statements like this:
    if node.chef_environment == 'development' do
      # do not configure basic auth
      #  configure basic auth
    What is important is that we should also write equivalent unit tests for such scenarios.
  2. Attributes: Use attributes to model the differences in environments.  In Chef, attributes can be overridden on per environment basis. This provides an elegant way to implement environment-specific changes. In the following example, the version of Ruby needs to be different in two environments.
    # the default attribute looks like
    default[:ruby][:version] = '1.8.7' 
    # the resource inside ruby recipe might use it like this
    ruby_version= 'ruby-'+ node[:ruby][:version]
    execute "download_ruby" do 
      cwd "/opt"
      command "wget -c http://someurl/#{ruby_version}.tgz"
    Then this can be overridden in all servers within the staging environment with this snippet
    name "staging"
  3. Environment-specific Chef recipes/components:  Use environment specific recipes and other components on the fly. If the amount of differences is large enough, involving multiple resources and significant configuration differences within a single file, you can use the environment name itself to dynamically load an environment-specific template or  environment-specific sub recipe. For example the deploy recipe invokes database backup as well as load balancer rotation (the instance should be out of the load balancer during the deployment) tasks that are only in production and not in any other environments. For this we can have a dedicated recipe name deploy::production. On the other hand the main deploy recipe uses something like this
    pre_deployment_sub_recipe = "deploy::#{node.chef_environment}_pre_deployment"
    include_recipe sub_recipe
    # Perform deployment
    post_deployment_sub_recipe = "deploy::#{node.chef_environment}_post_deployment"
    You can use similar strategies to include environment-specific configurations as well.  Here's an example:
    template "/etc/nginx/nginx.conf" do
      source "nginx." + node.chef_environment + "conf.erb"
    This one will pick up nginx.staging.conf.erb as a template in the staging environment's nginx configuration while nginx.production.conf.erb is in production environments. This does impose a problem because now every environment should have a corresponding sub-recipe or template. But again you can further scope this using one of the earlier two techniques. In this case, it is possible to do a file glob and pick up the environment-specific differences.

Learn how enterprises are using tools to automate security in their DevOps toolchain with these DevSecOps Reference Architectures.


Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}