Chef-dk tool set consists of following tools:
- chef
- kitchen
- berks
- chef-apply
- chef-client
- chef-shell
- chef-service-manager
- chef-solo
- chef-vault
- fauxhai
- foodcritic
- kitchen
- knife
- ohai
- rubocop
- shef
There are various tools which are used to automate tasks related to chef.
-
-
chef exec
Runs the command in context of the embedded ruby.
-
chef gem
Runs the
gem
command in context of the embedded ruby. e.g.,chef gem install gem_package
-
chef generate
Generate a new app, cookbook, or component.
-
chef app
Generate an application repochef gemerate app test
Above command will do the following things
- Creating directory name
test
- Creating kitchen file
.kitchen.yml]
- File
test/README.md
- Folder
cookbooks
- Folder
test
- File
metadata.rb
- File
chefignore
- File
Berksfile
- Folder
recipes
- file
default.rb
- file
- File
- Folder
- Creating kitchen file
- Now run
git init .
- Creating directory name
-
chef cookbook
Generate a single cookbook -
chef recipe
Generate a new recipe -
chef attribute
nerate an attributes file -
chef template
Generate a file template -
chef file
Generate a cookbook file -
chef lwrp
Generate a lightweight resource/provider -
cehf repo
Generate a Chef policy repository
-
-
shell-init
Initialize your shell to use ChefDK as your primary ruby
-
install
Install cookbooks from a Policyfile and generate a locked cookbook set
-
push
Push a local policy lock to a policy group on the server
-
-
Please note that berkshelf is only for the cookbook, not for the whole application.
-
Source code management tool.
-
A package Manager .
berks install # install all cookbook dependencies written in bershelf file
-
Replaces some portion of knife
-
cookbook generator, cookbook uploader, cookbook downloader.
-
berks cookbook NAME
: to Genrate cookbook -
berks init
: to convert an existing cookbookBerksfile
- berks fileThorfile
-chefignore
.gitignore
Gemfile
.kitchen.yml
test/integration/default
-
-
Please follow this pattern to make a cookbook.
-
Each cookbook contain each services like
loadbalancer, app_proxy server, caching server, worker_pool, database_server, etc.
cookbook::default cookbook:load_balancer cookbook::app_proxy cookbook::caching_server cookbook::worker_pool cookbook::db_server
-
-
It is unit test tool for cookbook. ChefSpec makes it easy to write examples and get fast feedback on cookbook changes without the need for virtual machines or cloud servers.
ChefSpec runs your cookbook(s) locally with Chef Solo or chefserver without actually converging a node. This has two primary benefits:
- It's really fast!
- Your tests can vary node attributes, operating systems, and search results to assert behavior under varying conditions.
The associated ChefSpec test might look like:
require 'chefspec' describe 'example::default' do let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) } it 'installs foo' do expect(chef_run).to install_package('foo') end end
/spec - folder for containing unit testcases
In General we divide our files in two :
-
Configuration file
-
/spec/spec_helper.rb
: the default file to define general configuration of all testsrequire 'chefspec' #to include chefspec in rspec require 'chefspec/berkshelf' #to include bershelf which is responsible for downloding external cookbook dependencies. # require 'chefspec/cacher' # The cacher module allows for ultra-fast tests by caching the results of a CCR in memory across an example group # require 'chefspec/librarian' # # Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f } RSpec.configure do |config| # Specify the path for Chef Solo to find cookbooks (default: [inferred from # the location of the calling spec file]) # config.cookbook_path = '/var/cookbooks' # Specify the path for Chef Solo to find roles (default: [ascending search]) # config.role_path = '/var/roles' # Specify the path for Chef Solo to find environments (default: [ascending search]) # config.environment_path = '/var/environments' # Specify the path to a local JSON file with Ohai data (default: nil) # config.path = 'ohai.json' config.platform = 'ubuntu' config.version = '12.04' config.log_level = :error end at_exit { ChefSpec::Coverage.report! }
-
Values specified at the initialization of the Runner merge and take precedence over the global settings:
Here Runner is
ServerRunner
orSoloRunner
# Override only the operating system version (platform is still "ubuntu" from above) ChefSpec::Runner.new(version: '10.04') # Use a different operating system platform and version ChefSpec::Runner.new(platform: 'centos', version: '5.4') # Specify a different cookbook_path ChefSpec::Runner.new(cookbook_path: '/var/my/other/path', role_path: '/var/my/roles') # Add debug log output ChefSpec::Runner.new(log_level: :debug).converge(described_recipe)
-
-
Test file
-
/spec/unit/recipe/default_spec.rb
: In this file we write the test cases.describe 'example::default' do let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) } it 'does something' do expect(chef_run).to ACTION_RESOURCE(NAME) end end
Where:
- ACTION - the action on the resource (e.g.
install
) - RESOURCE - the name of the resource (e.g.
package
) - NAME - the name attribute for the resource (e.g.
apache2
)
- ACTION - the action on the resource (e.g.
-
Describe : It specifies which recipe you want to test, For e.g.,
memsql::default
-
The common plugin/gems for chefspec which is used to enhance or modify its functionality are as:
-
require 'chefspec'
Create a temporary working directory and set ChefSpec's cookbook_path to the temporary directory.
-
require 'chefspec/berkshelf'
Download all the dependencies listed in your Berksfile into the temporary directory.
-
require 'chefspec/cacher'
The cacher module allows for ultra-fast tests by caching the results of a CCR in memory across an example group
In this we specify what to use in testing,
chefsolo
orchefzero
. This can be defined inlet
For e.g.,
-
CHEFSOLO
let(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) }
-
CHEFZERO
let(:chef_run) { ChefSpec::ServerRunner.converge(described_recipe) }
When we using ServerRunner we have to upload some data like client, databags, environment, etc to chefzero server, then we using following methods for that:
-
Create a client:
ChefSpec::ServerRunner.new do |node, server| server.create_client('my_client', { admin: true }) end
-
Create a data bag (and items):
ChefSpec::ServerRunner.new do |node, server| server.create_data_bag('my_data_bag', { 'item_1' => { 'password' => 'abc123' }, 'item_2' => { 'password' => 'def456' } }) end
-
Create an environment:
ChefSpec::ServerRunner.new do |node, server| server.create_environment('my_environment', { description: '...' }) end
-
Create a node:
ChefSpec::ServerRunner.new do |node, server| server.create_node('my_node', { run_list: ['...'] }) end
At the time of creation of node also define some ohai value, Known as
Fauxhai
objects.First Create and attribute object like,
www = stub_node(platform: 'ubuntu', version: '12.04') do |node| node.set['attribute'] = 'value' end # `www` is now a local Chef::Node object you can use in your test. To publish # this node to the server, call `create_node`: ChefSpec::ServerRunner.new do |node, server| server.create_node(www) end
-
Create a role:
ChefSpec::ServerRunner.new do |node, server| server.create_role('my_role', { default_attributes: {} }) end
NOTE The ChefSpec server is empty at the start of each example to avoid interdependent tests.
-
-
ChefSpec asserts that resource actions have been performed. In general, ChefSpec follows the following pattern:
-
Action and resource includes following helpful matchers.
-
include_recipe
Assert that the Chef run included a recipe from another cookbook
expect(chef_run).to include_recipe('other_cookbook::recipe')
-
notify
Assert that a resource notifies another in the Chef run
resource = chef_run.template('/etc/foo') expect(resource).to notify('service[apache2]').to(:restart).immediately
-
subscribes
Assert that a resource subscribes to another in the Chef run
resource = chef_run.service('apache2') expect(resource).to subscribe_to('template[/etc/foo]').on(:create).delayed
-
render_file
Assert that the Chef run renders a file (with optional content); this will match cookbook_file, file, and template resources and can also check the resulting content
expect(chef_run).to render_file('/etc/foo') expect(chef_run).to render_file('/etc/foo').with_content('This is content') expect(chef_run).to render_file('/etc/foo').with_content(/regex works too.+/)
You can use any RSpec content matcher inside of the with_content predicate:
expect(chef_run).to render_file('/etc/foo').with_content(start_with('# First line'))
-
package
expect(chef_run).to install_package('apache2')
If you want to specify
package('apache2').run_action(:install)
expect(chef_run).to install_package('apache2').at_compile_time
Similarly, you can assert that a resource is executed during convergence time:
expect(chef_run).to install_package('apache2').at_converge_time
Other forms
expect(chef_run).to install_package('apache2').with_version('1.2.3') expect(chef_run).to install_package('apache2').with(version: '1.2.3') expect(chef_run).to_not install_package('apache2') expect(chef_run).to purge_package('apache2') expect(chef_run).to purge_package('apache2').with(version: '1.2.3') expect(chef_run).to_not purge_package('apache2') expect(chef_run).to reconfig_package('apache2').with(version: '1.2.3') expect(chef_run).to remove_package('apache2').with(version: '1.2.3') expect(chef_run).to upgrade_package('apache2').with(version: '1.2.3')
-
do_nothing
Assert that a resource performs no action
resource = chef_run.execute('install') expect(resource).to do_nothing
-
Stubbing means creating a fake return from any asking things. Like if in my recipe if I used not_if with command then how chef_spec provide this in fake environment. So for this we have to make a stub command which define what to return if this ask.
For e.g., If I am using not_if in any template like
template '/tmp/foo.txt' do not_if 'grep text /tmp/foo.txt' end
Then we can stub the command what to return with
grep text /tmp/foo.txt
.before do stub_command("grep text /tmp/foo.txt").and_return(true) end
There are various types of stubbing. Some of them are:
-
Command
not_if 'grep text /tmp/foo.txt' #We can stub this by before do stub_command("grep text /tmp/foo.txt").and_return(true) end
-
Data Bag & Data Bag Item
Generally this is not required if you are using ChefSpec server, since you can upload databags directly in it.
Given a recipe that executes a data_bag method:
data_bag('users').each do |user| data_bag_item('users', user['id']) end
You can create a stub like this
before do stub_data_bag('users').and_return([]) end
You can also define multiple stubs in defore like:
describe 'example::default' do let(:chef_run) { ChefSpec::SoloRunner.new } before do stub_data_bag('users').and_return(['svargo', 'francis']) stub_data_bag_item('users', 'svargo').and_return({ ... }) stub_data_bag_item('users', 'francis') { (ruby code) } end end
If you are using encypted databags then
before do allow(Chef::EncryptedDataBagItem).to receive(:load).with('users', 'svargo').and_return(...) end
-
Search
Similar to the above it also not required in chefspec server.
before do stub_search(:node, 'name:hello').and_return([]) end before do stub_search(:node, 'name:hello') { (ruby_code) } end
-
Environment
let(:chef_run) do ChefSpec::SoloRunner.new do |node| # Create a new environment (you could also use a different :let block or :before block) env = Chef::Environment.new env.name 'staging' # Stub the node to return this environment allow(node).to receive(:chef_environment).and_return(env.name) # Stub any calls to Environment.load to return this environment allow(Chef::Environment).to receive(:load).and_return(env) end.converge('cookbook::recipe') end
-
Serverspec is another test platform that tests your actual servers via ssh. With Serverspec you can write RSpec test for checking your servers and configured correctly.
Similar to ChefSpec it has
decribe
in which we describe the whole test cases.describe "Git Daemon" do it "is listening on port 3306" do expect(port(3306)).to be_listening end it "has a running service of memsql" do expect(service("memsql")).to be_running end end
-
It is a QA tool for chef cookbooks which uses virtual instances for testing like vmware, virtualbox, EC2, etc. Its uses driver to connect these virtual instance service. To use kitchen we have the following steps:
-
initialize
kitchen init --driver=kitchen-vagrant create .kitchen.yml create test/integration/default create .gitignore append .gitignore append .gitignore
-
.kitchen.yml
file--- driver: name: vagrant provisioner: name: chef_solo platforms: - name: ubuntu-12.04 driver_config: network: - ['private_network', {ip: '192.168.1.1'}] - name: centos-6.4 driver_config: network: - ['private_network', {ip: '192.168.1.2'}] suites: - name: default run_list: - recipe[git::default] attributes:
-
driver
: This is where we configure the behaviour of the Kitchen Driver - the component that is responsible for creating a machine that we'll use to test our cookbook. Here we set up basics like credentials, ssh usernames, sudo requirements, etc. Each Driver is reponsible for requiring and using the configuration here. -
provisioner
: This tells Test Kitchen how to run Chef, to apply the code in our cookbook to the machine under test. The default and simplest approach is to usechef-solo
, but other options are available, and ultimately Test Kitchen doesn't care how the infrastructure is built - it could theoretically be with Puppet, Ansible, or Perl for all it cares. -
platforms
: This is a list of operation systems on which we want to run our code. Note that the operating system's version, architecture, cloud environment, etc. might be relevant to what Test Kitchen considers a Platform. -
suites
: This section defines what we want to test. It includes the Chef run-list and any node attribute setups that we want run on each Platform above. For example, we might want to test the MySQL client cookbook code seperately from the server cookbook code for maximum isolation.
-
-
List all machines
$ kitchen list Instance Driver Provisioner Last Action default-ubuntu-1204 Vagrant ChefZero <Not Created> default-centos-64 Vagrant ChefZero <Not Created>
-
Create a instance
$ kitchen create default-ubuntu-1204
-
upload cookbook and run chef client
kitchen converge default-ubuntu-1204
-
Kitchen verify it manually first
-
you can login to instance and manually check
kitchen login default-ubuntu-1204
-
-
Writing first integration testing
The component that helps facilitate testing on your instances is called
Busser
. To keep things simple we're going to use the busser-bats runner plugin which uses the Bash Automated Testing System also known as bats. You can also use any automated testing tool for this like-
make file in cookbook's folder
test/integration/default/bats/verify_installed.bat
@test "tmux is installed and in the path" { which tmux } @test "tmux configuration exists" { cat /etc/tmux.conf | grep "map" # this could be a more complex test }
-
As a reminder, the format for the path to a test is:
test/integration/SUITE/BUSSER/TEST
-
-
General structure of folders in kitchen
test
: shows it contains all test casesintegration
: contains all scripts of intehration testingSUITE
: which suite name you use in .kitchen.yml file- BUSSER : this tells kitchen which busser runner plugin is used. For e.g., bats
Test
: Testing scripts file
- BUSSER : this tells kitchen which busser runner plugin is used. For e.g., bats
-
kitchen test automatically
kitchen verify default-ubuntu-1204
-
you can also run all in one command
kitchen test
- A local configuration will be looked for in
.kitchen.local.yml
which could be used for development purposes. This is a file that is not typically checked into version control. - A global configuration file will also be looked for in
$HOME/.kitchen/config.yml
to set preferred defaults for all your projects.
- Integration testing using serverspec
-
Create a file
test/integration/server/serverspec/git_daemon_spec.rb
require 'serverspec' # Required by serverspec set :backend, :exec describe "Git Daemon" do it "is listening on port 9418" do expect(port(9418)).to be_listening end it "has a running service of git-daemon" do expect(service("git-daemon")).to be_running end end
-
-
-
Foodcritic has two goals:
-
To make it easier to flag problems in your Chef cookbooks that will cause Chef to blow up when you attempt to converge. This is about faster feedback. If you automate checks for common problems you can save a lot of time.
-
To encourage discussion within the Chef community on the more subjective stuff - what does a good cookbook look like? Opscode have avoided being overly prescriptive which by and large I think is a good thing. Having a set of rules to base discussion on helps drive out what we as a community think is good style.
-
To simple check the code :
foodcritic
-
-
It is used to automate test cookbooks. Guard monitors file system modifications and then takes actions based on what's defined in your Guardfile. And guard-kitchen is a plugin for Guard that auto-generates Test-Kitchen matchers for your Guardfile.
-
Guard init
repo2/cookbooks/hello
guard init
-
Guard start
guard start
Chef-Guard is a feature rich Chef add-on that protects your Chef server from untested and uncommitted cookbooks by running several validations and checks during the cookbook upload process. In addition Chef-Guard will also monitor, audit, save and email (including any difference with the actual change) all configuration changes, and is even capable of validating certain changes before passing them through to Chef.
Generally Guard is used to automate other tools like kitchen, foodcritic, etc. which automatically launch the script on the basis of file system change specified in watchers.
For e.g.,
-
Automate kitchen testing
# A sample Guardfile # More info at https://github.com/guard/guard#readme guard 'kitchen' do watch(%r{test/.+}) watch(%r{^recipes/(.+)\.rb$}) watch(%r{^attributes/(.+)\.rb$}) watch(%r{^files/(.+)}) watch(%r{^templates/(.+)}) watch(%r{^providers/(.+)\.rb}) watch(%r{^resources/(.+)\.rb}) end
-
Automate foodcritic
# A sample Guardfile # More info at https://github.com/guard/guard#readme guard "foodcritic" do watch(%r{attributes/.+\.rb$}) watch(%r{providers/.+\.rb$}) watch(%r{recipes/.+\.rb$}) watch(%r{resources/.+\.rb$}) watch(%r{^templates/(.+)}) watch('metadata.rb') end
-