├── LICENSE ├── README.md ├── monorepo └── README.md └── the_berkshelf_way └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Pulse Energy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chef Style Guide 2 | 3 | *And other assorted best practices when using Chef.* 4 | 5 | ## Table of Contents 6 | 1. [Other Style Guides](#other-style-guides) 7 | 1. [Repository Structure](#repository-structure) 8 | 1. [Attributes](#attributes) 9 | 1. [Cookbooks](#cookbooks) 10 | 1. [Providers](#providers) 11 | 1. [Recipes](#recipes) 12 | 1. [Templates](#templates) 13 | 1. [Conditionals](#conditionals) 14 | 1. [Environments](#environments) 15 | 1. [Roles](#roles) 16 | 1. [Data Bags](#data-bags) 17 | 1. [Nodes](#nodes) 18 | 1. [Test Kitchen](#test-kitchen) 19 | 1. [Secrets](#secrets) 20 | 1. [Debugging](#debugging) 21 | 1. [Common](#common) 22 | 23 | ## Other Style Guides 24 | 25 | - Read [Patterns To Follow](https://docs.chef.io/ruby.html#patterns-to-follow) in the official Chef Style Guide. Some points have been copied into this guide. 26 | 27 | - Foodcritic is an excellent lint tool for Chef cookbooks. This style guide will skip over anything that is already automated by Foodcritic. 28 | 29 | - https://github.com/ampledata/cookbook-style-guide 30 | 31 | - https://github.com/johnbellone/chef-style-guide#rubocop 32 | 33 | - https://github.com/facebook/chef-utils/blob/master/Philosophy.md 34 | 35 | - https://github.com/infochimps-labs/ironfan/wiki/style_guide 36 | 37 | ## Repository Structure 38 | 39 | - Create a new git repository for each cookbook. This is known as [The Berkshelf Way](the_berkshelf_way/). 40 | 41 | Not so sure about this? Or you're stuck maintaining Chef with many cookbooks in one repository? We still have some guidance on how to make the best of using a [monorepo](monorepo/). 42 | 43 | ## Attributes 44 | 45 | - Never use `node.set` or `node.normal`. Creating persistent node attributes is almost universally a bad idea. 46 | 47 | A normal Chef run starts with an empty set of attributes and populates them based on cookbooks, environments, and roles, all of which are visible in the source code. 48 | 49 | Attributes persisted onto a node are never visible in source code. They can produce confusing behaviour that is difficult to troubleshoot. The stored node attributes will impact all future runs until you manually remove them from the node. 50 | 51 | > "Normal and override attributes are cleared at the start of the chef-client run, and are then rebuilt as part of the run based on the code in the cookbooks and recipes at that time." - [Patterns To Follow](https://docs.chef.io/ruby.html#patterns-to-follow) 52 | 53 | - Start with a minimal set of attributes for your cookbook. You can easily add new attributes later, but removing attributes always means breaking compatibility, and that means you have to increment the major version. 54 | 55 | - All attributes defined by your cookbook should exist within your cookbook's namespace. 56 | 57 | ```ruby 58 | # Attributes:: external 59 | default['my_cookbook']['my_attribute'] = 'foo' 60 | ``` 61 | 62 | - If you need to read attributes from another cookbook, copy them into the namespace of your own cookbook. Do this inside `attributes/external.rb`. Try to minimize the number of external attribute accesses. 63 | 64 | Use `include_attribute` to create sections within your `external.rb`. Although `include_attribute` is only a hint to the Chef compiler, it makes it much easier to search for dependencies in a code base when you want to refactor a cookbook. 65 | 66 | ```ruby 67 | # Attributes:: external 68 | include_attribute 'other_cookbook::default' 69 | default['my_cookbook']['limit'] = node['other_cookbook']['limit'] 70 | ``` 71 | 72 | - Never override other cookbooks' attributes inside your recipes. Always use `attributes/override.rb`. 73 | 74 | Using `node.default` and `node.override` in recipes, especially when overriding values used by other cookbooks, is risky because you cannot be sure that your recipe will run first. Your overrides will effectively be ignored, but your changes will still appear in the attributes of the node object saved to Chef server. This can be very difficult to debug. 75 | 76 | The best place to override attributes is in `attributes/override.rb`. Attributes files are normally used to define the inputs to your cookbook. By putting overrides into a specific file, you make it clear that they are not inputs. 77 | 78 | It is good practice to use `include_attribute` to include the attribute file that contains the values you are overriding. This provides fail-fast behaviour if the attribute file or the cookbook no longer exist. It also doubly ensures that your override takes precedence over the original value, even if they are at the same precedence level. 79 | 80 | ```ruby 81 | # Attributes:: override 82 | include_attribute 'other_cookbook::default' 83 | override['other_cookbook']['foo'] = 'my_override_value' 84 | ``` 85 | 86 | The only case where it's acceptable to override other cookbooks' attributes in your recipes is if you know that the cookbook you're overriding is using lazy attribute evaluation. However, this is exceedingly rare in the Chef ecosystem. 87 | 88 | An earlier version of this guide provided the opposite advice, suggesting that overrides should always be placed in recipes. However, it turns out that creates more problems than it solves, so the advice has been updated. 89 | 90 | - Avoid derived attributes in attribute files. 91 | 92 | ```ruby 93 | # Attributes:: default 94 | // DO NOT DO THIS 95 | default['my_cookbook']['version'] = '1.4.8' 96 | default['my_cookbook']['url'] = "http://mysite/#{node['my_cookbook']['version']}.tar.gz" 97 | ``` 98 | 99 | If somebody overrides the `version` attribute, the `url` attribute won't be recomputed. It doesn't matter whether the attribute is overridden in an environment file or by another cookbook. 100 | 101 | When you need to derive attributes, do it inside a recipe or provider. Or use the [Delayed Interpolation](https://coderanger.net/derived-attributes/) tip by Noah Kantrowitz. 102 | 103 | - If you need to define attributes that aren't really owned by any particular cookbook, use `node['global']` as the namespace and define them in roles or environment files. It is usually preferable to have attributes owned by a cookbook, but sometimes there are exception cases and the exceptions should be clearly marked. 104 | 105 | - Always use hashes for complicated attributes. Avoid using arrays. 106 | 107 | > "Due to the precedence merging algorithm Chef uses, Arrays are very difficult to work with." - [Our Experiences with Chef: Cookbook and Attribute Design](https://omniti.com/seeds/seeds-our-experiences-with-chef-cookbook-and-attribute-design) 108 | 109 | > "With hashes, this merge is relatively straight forward, if both the original and override values are hashes they are recursively merged together all the way down. This logic is less clear on arrays though. In Chef 11, the behavior is that on different precedence levels, the higher value overrides as with strings and other simple types." - [Arrays and Chef Attributes, Noah Kantrowitz](https://coderanger.net/arrays-and-chef/) 110 | 111 | - Expect attributes to change between runs of chef-client. If your recipe cannot cope with an attribute changing, perhaps it should not be an attribute. Or alternately, your recipe might preserve the attribute using node.normal to ensure that it remains constant for that node on subsequent runs. This can be helpful if you want to configure new nodes in an environment differently. 112 | 113 | ## Cookbooks 114 | 115 | - Use [Semantic Versioning](http://semver.org/) on any cookbook that is uploaded to Chef Server or published to Supermarket. Every cookbook declares a public API, and thus it is appropriate to use SemVer. 116 | 117 | If you aren't using Test Kitchen, this means bumping the patch version every time you try a new change. You should be using Test Kitchen. 118 | 119 | When creating a new cookbook, move quickly to create a 1.0 version. You should definitely not spend more than a week creating 0.x.x releases. Cookbooks rapidly become dependent on each other, so you should acknowledge that you have a public interface by issuing a 1.0 release. 120 | 121 | - Prefer using optimistic version constraints in your cookbook dependencies rather than [pessimistic version constraints](https://stackoverflow.com/questions/4292905/what-is-the-difference-between-and-when-specifying-rubygem-in-gemfile) especially in community cookbooks. 122 | 123 | A single pessimistic version constraint in a deep transitive dependency is enough to block usage of new cookbooks that are, in many cases, perfectly safe to use. It's common practice in the Chef community to bump the major version of a cookbook when dropping support for old versions of chef-client, but otherwise keep the API mostly intact. 124 | 125 | Use an optimistic version constraint (like `>= 1.4.0`) if you require particular functionality that is somewhat recent. Use a blocking version constraint (like `<= 3.0.0`) if you depend on a cookbook but are not yet compatible with recent versions. Only use pessimistic constraints (like `~> 1.0`) on dependencies in cookbooks that are private and not likely to have other cookbooks depend on them. 126 | 127 | - Never *ever* decrement the version of a cookbook. Failure to adhere is a violation of [Rule #2 in SemVer 2.0](http://semver.org/#spec-item-2). 128 | 129 | Chef-client will always use the highest-numbered cookbook that is available after considering all constraints. If Chef Server knows about a cookbook with a higher number than the one you just uploaded, then your code is not going to get run. Do not add a version constraint in your test environment to work around this; it will definitely bite you later on. 130 | 131 | Your build system should fail the build if the cookbook version has not been incremented beyond the last uploaded cookbook. This matters even more if you're publishing to Supermarket. 132 | 133 | - Always freeze your cookbooks when uploading them. Failure to do so is a violation of [Rule #3 in SemVer 2.0](http://semver.org/#spec-item-3). 134 | 135 | ``` 136 | knife cookbook upload my_cookbook --freeze 137 | ``` 138 | 139 | - Include a `CHANGELOG.md`. Focus on clear documentation of breaking changes. Be terse with everything else. Any changes that are not backward-compatible should bump the major version and warrant an entry in the CHANGELOG so that people know how to become compatible with the latest version. 140 | 141 | - Include a `README.md`. Use only markdown, never HTML. Supermarket will not render HTML in your README and it ends up looking terrible. 142 | 143 | - If you have two cookbooks with many dependencies that need to obtain a shared attribute, consider moving that attribute into a new lightweight cookbook or a role if it will be consistent across environments. This helps to avoid gigantic dependency chains that Berkshelf will struggle to resolve. Do not put it into an environment file if you expect it to be the same in all environments. 144 | 145 | - If you are publishing to Supermarket, use [Stove](https://github.com/sethvargo/stove) to automate the process. 146 | 147 | ## Providers 148 | 149 | - If you include a provider in your cookbook, the cookbook name should not include hyphens. The name of a provider includes the cookbook name, but provider names never use hyphens, only underscores. So if your cookbook is named with a hyphen, it will make it harder to search for uses of the provider in your code. 150 | 151 | - Always `use_inline_resources` at the start of your provider. This is a kind of "strict mode". This is expected to become the default behaviour in Chef 13. 152 | 153 | > "use_inline_resources is used to separate a LWRP's run context from the main run, making it run in an isolated mini-run. You cannot subscribe to / notify resources that are not part of this resource's context." - [Backslasher](http://blog.backslasher.net/chef-inline.html) 154 | 155 | - Use providers instead of recipes when writing reusable cookbooks. Providers have the benefit that they can be instantiated multiple times within a single Chef run. This comes up more often than you would expect. 156 | 157 | - Avoid unnecessary recipes in your provider cookbooks. They only serve as a distraction and maintenance burden. 158 | 159 | - Always define a `:remove` action to clean up resources created by your provider. 160 | 161 | Imagine you have written a provider that installs an application into `new_resource.prefix`. Then somebody who is using your provider in a recipe decides to install to a different prefix. How should they clean up anything left behind? The answer is that they should change the action on the existing provider to `:remove` and declare a new resource using your provider with the new path. The *tombstone* resource needs to be left in the configuration long enough for all nodes to converge. 162 | 163 | Some people can sidestep this by using disposable infrastructure, but many people do not have that luxury. 164 | 165 | ## Recipes 166 | 167 | - Include a `default.rb` recipe but leave it empty. Avoid the tempation to include sub-recipes inside `default.rb`. Create other recipes with specific functions. Recipes are the main public interface of your cookbook, and consumers are expected to know about them. 168 | 169 | > "Don’t use the default recipe (leave it blank). Instead, create recipes called server or client (or other)." - [Patterns To Follow](https://docs.chef.io/ruby.html#patterns-to-follow) 170 | 171 | - Private recipes should start with an underscore. The absence of an underscore is an indicator that the recipe is meant for use in run lists, or included from recipes in other cookbooks. 172 | 173 | - Create a `_common.rb` recipe if there are multiple sub-recipes that would otherwise define the same resources (eg. a directory). Otherwise you might find yourself running into [CHEF-3694](http://tickets.chef.io/browse/CHEF-3694) warnings. 174 | 175 | - Use `node[cookbook_name]` to refer to attributes within your cookbook's namespace. If you ever copy or rename the cookbook, you won't need to make nearly as many changes. *Caveat:* `cookbook_name` is not defined in attribute files, so you need to set it as a local variable if you want to use it. 176 | 177 | ```ruby 178 | # Attributes:: default 179 | cookbook_name = 'my_cookbook' 180 | default[cookbook_name]['svc_user'] = 'alice' 181 | ``` 182 | 183 | ```ruby 184 | # Recipe:: server 185 | user node[cookbook_name]['svc_user'] do 186 | action :create 187 | end 188 | ``` 189 | 190 | - Use [node.run_state](https://docs.chef.io/recipes.html) to stash transient data during a chef-client run. The advantage of run_state is that values are not saved back onto the node object, so the run_state is suitable for semi-private values and larger data structures. 191 | 192 | ## Templates 193 | 194 | - Avoid using the `node` object within templates. 195 | 196 | If you need to use attributes in a template, add them to the template resource using the `variables` parameter. 197 | 198 | ```erb 199 | // GOOD: 200 | Alice is <%= @age %> years old. 201 | // BAD: 202 | Alice is <%= node['my_cookbook']['alice']['age'] %> years old. 203 | ``` 204 | 205 | Never refer to attributes from other cookbooks in your template. 206 | 207 | - Use the ERB comment syntax for comments that don't need to be rendered into the file. 208 | 209 | <%# 210 | This is an ERB comment 211 | %> 212 | 213 | ## Conditionals 214 | 215 | - If you need to do something environment-specific, use an attribute instead. Never make conditionals dependent on the name of an environment. 216 | 217 | - Understand [Chef's Two Pass Model](https://coderanger.net/two-pass/). Be aware of the difference between `if` statements and guards. One of them is handled during the compile phase, and the other during the execute phase. 218 | 219 | ## Environments 220 | 221 | - Environment files are great. Prefer them instead of data bags. Everything in the environment is available at the beginning of the run, whereas data bags require additional HTTP requests. 222 | 223 | - Avoid keeping secrets in Chef. If you must keep them in Chef, then environment files are the natural place for them. But consider using something like [Vault](https://github.com/hashicorp/vault) instead. 224 | 225 | - Avoid tracking the current addresses of backing services in your environment file. If you must keep them in Chef, environment files are the natural place. But consider using a proper Service Discovery tool like [Consul](https://www.consul.io/) or Zookeeper instead. 226 | 227 | - Environment names are always uppercase. 228 | 229 | The name specified within the environment file should be an exact case-sensitive match of the filename. 230 | 231 | When you specify an environment with chef-zero it looks for a file with exact matching case. If the filename has different case than the usage in your recipes, you will have difficulty with matching and lookups. *(Although admittedly, you should never depend on the name of an environment within your code. Use attributes for that instead.) 232 | 233 | ## Roles 234 | 235 | - Minimize the number of roles in the run_list of each node. Typically a node should have one role, or two if you have separated base machine configuration from application configuration. This shifts complexity away from node management (which is ephemeral) to role management (which is tracked in version control). 236 | 237 | - Avoid setting attributes in roles. It's often confusing. 238 | 239 | ## Data Bags 240 | 241 | - Do not use data bags to store data that is different across environments. Environment files are very good at this already. Having two places to look ends up being very confusing. 242 | 243 | - One of the motivations to use data bags instead of attributes in environment files is to be selective about what data is downloaded and uploaded. If you have some large data bags that are only used by a small subset of your nodes, then you'll be wasting a lot of bandwidth by populating the node hierarchy with unwanted data, and then wasting more bandwidth when the node hierarchy is saved back to the Chef Server at the end of the run. 244 | 245 | - Avoid using data bags in public cookbooks. 246 | 247 | > Requiring a certain data bag structure forces people to manage their infrastructure in a certain manner. This is a violation of one of the guiding principles of Chef: you know your infrastructure best. The `users` cookbook is a big culprit here. It forces users to conform to a certain data structure, which rarely meets the ever-changing and unique demands of an organization. - [Seth Vargo](https://www.chef.io/blog/2014/01/23/attributes-or-data-bags-what-should-i-use/#comment-1213702855) 248 | 249 | - Before making structural changes to a data bag you must make your cookbook forward-compatible, and the cookbook must be promoted to all environments. 250 | 251 | Do not try to modify a data bag and a cookbook simultaneously. Data bags are atomic, and so are cookbooks. If you try to modify them simultaneously, you will end up with converge failures or worse. 252 | 253 | 1. Make your cookbook forward-compatible 254 | 2. Promote your cookbook to all environments 255 | 3. Change the data bag 256 | 4. Simplify your cookbook 257 | 5. Promote your cookbook to all environments 258 | 259 | - Minimize the number of data bag lookups that occur. A new HTTP request is sent every time you request a data bag. 260 | 261 | - Create a `data_bag.rb` recipe in each cookbook that needs to look up something from a data bag. Put all of your data bag lookups into this recipe. This helps to reduce duplicate lookups. 262 | 263 | - Try to name your data bag so that it matches the name of your cookbook. But due to the shared nature of data bags, this is often not possible. 264 | 265 | - If you are using your data bag as a collection, each item in the data bag should include an `action` attribute to explicitly define whether resources should be created or deleted. 266 | 267 | If you don't specify this, then you have the implicit assumption that all resources in the data bag will result in resource creation. Then you will have a bad time when you want to remove something from the data bag, because remnants will get left behind. 268 | 269 | Chef is only able to act on the presence of resources, not the absence of them. In order to clean up resources effectively, you need to use *tombstones* that last long enough for all nodes to finish converging the deletion. 270 | 271 | The exception is when your collection is used to render a single resource, such as when a template file is populated by all the items of a data bag. In this case, any data bag entries that are removed will automatically disappear from the template. 272 | 273 | ## Nodes 274 | 275 | - Use only one entry in the run_list for each node. Node run lists do not have the benefit of version control, and become very difficult to maintain once they grow long enough. 276 | 277 | - Use a `base` cookbook for the essentials that are needed on every single node. 278 | 279 | - If you have something that isn't needed on every node, it does not belong in base. A good example of what goes in a base cookbook is users and SSH configuration. 280 | - Only add your `base` cookbook to Chef roles, never to a node run_ _list. Nodes should have only one item in the run list, and it should be a role. 281 | - Application cookbooks should never depend on the base cookbook. 282 | - Always add your `base` cookbook to the **end** of your run_list. Adding it at the end instead of the beginning helps to ensure that application cookbooks don't have hidden dependencies on your `base` cookbook. 283 | - Try hard to minimize the number of cookbook dependencies used by your base cookbook. 284 | 285 | ## Test Kitchen 286 | 287 | - Use Test Kitchen. Include a `.kitchen.yml` file with every cookbook. Make sure your cookbook converges locally before promoting or publishing your cookbook. This helps with http://12factor.net/dependencies. 288 | 289 | - Always test against the latest version of Chef. You might want to pin a specific version of Chef to make Kitchen run faster, but remember to keep it updated. 290 | 291 | - In `.kitchen.yml` all references to environments and data bags should be contained within the cookbook. This only applies if your cookbooks are stored in a monorepo. Any references outside the cookbook (ie. to a directory of shared environments and data bags) makes it too easy to overlook implicit dependencies. Using local environment and data bag files is an easy way to explicitly show and test those attribute dependencies. 292 | 293 | ## Secrets 294 | 295 | - Try hard to avoid keeping secrets in Chef. Consider something like Hashicorp Vault. 296 | 297 | ## Debugging 298 | 299 | - The very best output comes from executing chef-client with the debug log level while running as root user. 300 | 301 | ``` 302 | chef-client --log_level debug 303 | ``` 304 | 305 | ## Common 306 | 307 | - Use ChefDK to install your development environment. 308 | 309 | - All files should end with a linefeed. Vim does this for you. Sublime Text can be configured to do this, but does not do it by default. 310 | 311 | - Define arrays on multiple lines, and put a comma at the end of every line. This is one of the best features of Ruby. 312 | 313 | ```ruby 314 | # roles/my_role.rb 315 | run_list [ 316 | "recipe[my_app::deploy]", 317 | "recipe[my_base::all]", 318 | ] 319 | ``` 320 | 321 | - Avoid hardcoding node names (or patterns to match node names) anywhere in your recipes. Use attributes instead. 322 | 323 | - If you want to serialize part of the node hierarchy to YAML, convert it to JSON first to wash off the cruft. 324 | 325 | Node attributes aren't regular Ruby hashes, they are objects of type `Chef::Node::ImmutableMash`. When you serialize to YAML, you get `ImmutableMash` strings [littered all around](http://lists.opscode.com/sympa/arc/chef/2014-02/msg00054.html). Somehow the JSON serializer does not suffer from this problem. 326 | 327 | ```ruby 328 | my_yaml = JSON.parse(my_object.to_json).to_yaml 329 | ``` 330 | 331 | - When you have some code that can definitely be removed at a future date or when some future condition is met, add an `# UNTIL:` comment that specifies the removal condition. Most *tombstone* resources (those with a `:delete` action) should include an `UNTIL:` comment. 332 | 333 | ## Unresolved questions 334 | 335 | - Where is the best place to apply version constraints? 336 | - What should you do with Chef logfiles? Can they be shipped to Splunk or Logstash easily? 337 | -------------------------------------------------------------------------------- /monorepo/README.md: -------------------------------------------------------------------------------- 1 | # Chef with a Monorepo 2 | 3 | - Any commit affecting a cookbook must only include changes to files within that cookbook. 4 | 5 | Do not fall into the trap of thinking that all commits to the monorepo are atomic, because they aren't. All those cookbooks and data bags are being deployed independently of each other. 6 | 7 | When you allow your commits to cross those boundaries, it's easy to forget that you need to be careful about maintaining compatibility between each piece of the system. 8 | 9 | - Avoid downloading community cookbooks into your monorepo. Use Berkshelf or Chef Librarian to resolve and upload the dependencies. 10 | 11 | If you really need to download community cookbooks into your repository, then at least follow this one rule: Never *ever* make changes to community cookbooks in your repository. This will make it incredibly difficult to take advantage of upstream fixes and improvements. 12 | 13 | - Read `metadata.json` in your scripts. You're going to have scripts, and they're probably going to care about your cookbook versions. Those cookbooks downloaded from Supermarket will not have a metadata.rb file. 14 | -------------------------------------------------------------------------------- /the_berkshelf_way/README.md: -------------------------------------------------------------------------------- 1 | # Chef, The Berkshelf Way 2 | 3 | [The Berkshelf Way – Jamie Winsor](https://www.chef.io/blog/chefconf-talks/the-berkshelf-way-jamie-winsor/) (2013). 4 | 5 | ## Background 6 | 7 | The old way is to think of nodes as the first-class citizen. You always start with a node. Then you run chef-client to get the configuration for that node. Chef Server decides which cookbooks fit the version constraints and delivers them in a batch. The cookbooks are merely interlocking components of The Chef Run. 8 | 9 | In contrast, The Berkshelf Way elevates cookbooks to standalone, self-contained applications that can be executed on their own schedule. 10 | 11 | When you start treating cookbooks as applications, you will see that many of the principles from [The Twelve-Factor App](http://12factor.net/) methodology are applicable. 12 | 13 | ## Differences 14 | 15 | - Version Indepdendence 16 | 17 | Imagine you have two cookbooks ("A" and "B") that need to run on the same node, and they both depend on a third cookbook "C". The maintainers of "C" release a new major update which is not backward compatible. But if you want to use it, then both "A" and "B" need to be updated simultaneously. 18 | 19 | But when your cookbook becomes a self-contained application, you can update "A" and "B" each on their own schedule without worrying about conflicting version constraints. 20 | 21 | - Scheduling Diversity 22 | 23 | If your only way to use Chef is by kicking off a massive monolithic Chef run, then running Chef twice an hour sounds reasonable enough. 24 | 25 | But what about those times when just want something done fast? Maybe you're deploying an application, and you want to ensure that the configuration file is up to date. Do you want to spend 120 seconds for chef-client to run on each node, or do you want spend 10 seconds to run only the portions that apply to your application? 26 | 27 | And some things just don't need to happen that often. Maybe you have a Chef cookbook that installs some favourite tools for interactive SSH users. That would probably be fine to run on a nightly basis. 28 | 29 | - Docker Alignment 30 | 31 | Want to start using containers? Docker is an application-first technology, so The Berkshelf Way is better aligned with it. If you need to provision dependencies and configuration files inside a container before deploying it, using Chef with a lightweight application cookbook will do just fine. 32 | 33 | ## Do I really need separate repos? 34 | 35 | Some people find it easier to embrace The Berkshelf Way when they create a new git repository for each cookbook, but that is not strictly required. There are clever ways to use [Berkshelf with multiple cookbooks](http://chef.opscode.narkive.com/z3bZlv9b/single-repo-vs-repo-per-cookbook) in a single git repo. If you have already embraced The Berkshelf Way, there are only a few benefits to be gained by moving to a single repo per cookbook. 36 | 37 | - You can bring the configuration code much closer to application code through submodules or even by putting the cookbook into the application repo. 38 | - You avoid tag confusion, because git tags are global. 39 | - Merges are guaranteed to contain only relevant commits. 40 | - You can use simplified build scripts. 41 | 42 | ## Style 43 | 44 | - Every cookbook has its own repository. 45 | 46 | - Create a git tag every time you upload to Chef Server or Supermarket. 47 | 48 | - Create a `release` branch to use for automated releases. When you're ready to release changes, merge them into this branch. 49 | 50 | Trigger a new release to Chef Server and/or Supermarket every time this branch is updated. 51 | 52 | Your merged changes must also have incremented the version number. Your build system should fail the build if the version has not been bumped. 53 | 54 | If you're uploading cookbooks to Chef Server, use `--freeze` as extra insurance to guarantee that you never overwrite a previous cookbook artifact. 55 | 56 | # Unresolved 57 | 58 | - If you have an application and you're creating a cookbook for it, does it make sense to keep the application and its cookbook in the same repository? Seems like it would be nice to be able to update the application code and dev configuration files and chef cookbook all in one go. But then you end up with a polluted tag history, changesets that include irrelevant changes, etc. Could git submodules make a difference here? 59 | 60 | --------------------------------------------------------------------------------