Local-only Knife Configuration
In this post I want to discuss briefly an approach to setting up a shared Knife configuration file for teams using the same Chef Repository, while supporting customized configuration.
Background
Most infrastructures managed by Chef have multiple people working on them. Recently, several people in the Ruby community started working together on migrating RubyGems to Amazon EC2.
The repository has a shared .chef/knife.rb
which sets some local
paths where cookbooks and roles are located. In addition to this, I
wanted to test building the infrastructure using a Chef Server and my
own EC2 account.
The Approach
At Opscode, we believe in leveraging internal DSLs. The
.chef/knife.rb
(and Chef’s client.rb
or solo.rb
, etc) is no
exception. While you can have a fairly simple configuration like this:
node_name "jtimberman"
client_key "/home/jtimberman/.chef/jtimberman.pem"
chef_server_url "https://api.opscode.com/organizations/my_organization"
cookbook_path "cookbooks"
You can also have something like this:
log_level :info
log_location STDOUT
node_name ENV["NODE_NAME"] || "solo"
client_key File.expand_path("../solo.pem", __FILE__)
cache_type "BasicFile"
cache_options(path: File.expand_path("../checksums", __FILE__))
cookbook_path [ File.expand_path("../../chef/cookbooks", __FILE__) ]
if ::File.exist?(File.expand_path("../knife.local.rb", __FILE__))
Chef::Config.from_file(File.expand_path("../knife.local.rb", __FILE__))
end
This is the knife.rb
included in the
RubyGems-AWS repo.
The main part of interest here is the last three lines.
if ::File.exist?(File.expand_path("../knife.local.rb", __FILE__))
Chef::Config.from_file(File.expand_path("../knife.local.rb", __FILE__))
end
This says “if a file knife.local.rb
exists, then load its
configuration. The Chef::Config
class is what Chef uses for
configuration files, and the #from_file
method will load the
specified file.
In this case, the content of my knife.local.rb
is:
node_name "jtimberman"
client_key "/Users/jtimberman/.chef/jtimberman.pem"
validation_client_name "ORGNAME-validator"
validation_key "/Users/jtimberman/.chef/ORGNAME-validator.pem"
chef_server_url "https://api.opscode.com/organizations/ORGNAME"
cookbook_path [
File.expand_path("../../chef/cookbooks", __FILE__),
File.expand_path("../../chef/site-cookbooks", __FILE__)
]
knife[:aws_access_key_id] = "Some access key I like"
knife[:aws_secret_access_key] = "The matching secret access key"
Here I’m setting my Opscode Hosted Chef credentials and server. I also
set the cookbook_path
to include the site-cookbooks directory (this
should probably go in the regular knife.rb). Finally, I set the knife
configuration options for my AWS EC2 account.
The configuration is parsed top-down, so the options here that overlap
the knife.rb
will be used instead.
In the Repository
In the repository, commit only the .chef/knife.rb
and not the
.chef/knife.local.rb
. I recommend adding the local file to the
.gitignore or VCS equivalent.
% echo .chef/knife.local.rb >> .gitignore
% git add .chef/knife.rb .gitignore
% git commit -m 'keep general knife.rb, local config is ignored'
Conclusion
There are many approaches to solving the issue of having shared Knife configuration for multiple people in a single repository. The real benefit here is that the configuration file is Ruby, which provides a lot of flexibility. Of course, when using someone else’s configuration examples, one should always read the code and understand it first :-).