This is a library cookbook that adds support for rule-based feature flags to Chef recipes. The primary use case is designating a particular number or percentage of machines in a Chef environment to receive a new feature without having to modify individual nodes with tagging or manual editing. This is useful for canaries and gradual rollouts of new features.
- Chef 12.1+
There is a test cookbook under test/pd-feature-test
that contains examples for all supported rules.
To use the library, add this cookbook to your metadata.rb
:
depends 'pd-feature'
Then implement features in your recipes, using feature_enabled?
to check the feature flag, like so:
if feature_enabled?('name_of_feature')
new_hotness
else
old_code
end
Finally, write the feature flag rules. The rules are stored as Chef attributes. For a cookbook named 'foo' and a feature named 'bar', the attribute should be named like this:
default['foo']['feature_bar'] = false
The simplest rules are true
(feature is always enabled) and false
(feature is always disabled). Note that they are not strings.
default['foo']['feature_bar'] = 'count:3'
In this example, the feature is enabled for the first 3 machines in every Chef environment the recipe runs. Namely, if you have two Chef environments, alpha
and bravo
with 5 machines each, the feature will be enabled on 6 machines, 3 in alpha
and 3 in bravo
.
default['foo']['feature_bar'] = 'percent:51'
In this example, the feature is enabled for half the machines in every Chef environment the recipe runs. Namely, if you have two Chef environments, alpha
and bravo
with 2 and 4 machines respectively, the feature will be enabled on 3 machines, 1 in alpha
and 2 in bravo
.
Percentages are interpreted as floats, so using a slightly bigger value avoids float comparison issues with small number of machines: use 34
for one third, for example. The number of machines chosen is rounded down, so in a small enough environment and percentage you could end up with no machines chosen (for example, 5 machines and percent:10
).
default['foo']['feature_bar'] = 'role'
default['foo']['feature_bar_app'] = 'count:2'
In this example, the feature is enabled for the first two machines in a Chef environment the recipe runs that have the Chef role app
. We are using expanded matching, which means you can match on either a role specified in the node run-list or a role that is included by a role or recipe but not explicitly named in the node's run-list.
Note that when the recipe containing the feature is already role-specific, you do not need this rule; this is for the rare case when the recipe is shared across multiple roles and you want to enable the feature on a particular role in a specific Chef environment.
What does it mean "the first X machines" or "the first Y percent"? The implementation guarantees the order is stable (successive Chef runs will select the same machines), but the specific order is an implementation detail and might change. Currently an alphabetical sort by node name is used.
Rules can be combined:
default['foo']['feature_bar'] = 'count:1,percent:26'
means at least one node will be chosen, and in large environments about a quarter of nodes will be chosen. The operator is "OR" and the first match that enables the feature stops the evaluation.
You cannot have a simple rule (true
or false
) in a combination, because it is pointless: false
is implied (feature is disabled if it is not enabled by any rule), and true
would always enable the feature, rendering the rest of the clauses unnecessary.
By default, all rules are evaluated in the context of the Chef environment a node is located in. You can specify any other context using the optional search_scope
parameter. It will replace the environment-specific search. This can be used select more complicated subsets of nodes or to apply a feature to the entire infrastructure with a global search like node:*
.
if feature_enabled?('name_of_feature', search_scope: 'cloud_provider:ec2')
aws_specific_new_hotness
end
If you use chef-client to run playbooks, you can use the --json-attributes
option to manually enable or disable a feature flag:
$ echo '{"mycookbook":{"feature_jumboframes":true}}' > /tmp/jumbo.json
$ chef-client -j /tmp/jumbo.json
The cookbook outputs a bunch of information on the DEBUG
log level to help you understand which features are enabled and why. Here are a few examples:
DEBUG: Feature check: 'simple' is enabled by attribute
DEBUG: Feature check: this node's position for feature 'foo' is 2 out of 3
DEBUG: Feature check: 'bar' is enabled by rule 'percent:34'
Note that this logic normally runs during Chef's compile phase, and resources run during the converge phase, so the debugging output might be located a long distance away from the output from the resources the feature controls.
- Fork the repository on Github
- Create a named feature branch (like
add_component_x
) - Write your change
- Write tests for your change
- Run the tests, ensuring they all pass. Run
cookstyle
. Runfoodcritic
. - Submit a Pull Request using Github
Author: Max Timchenko (@maxvt)
License: Apache 2. See the LICENSE
file for full licensing information.