Alexey Vasiliev aka leopard
leopard.in.ua
Cooking
infrastructure
by
Chef
Cooking Infrastructure by Chef
Alexey Vasiliev aka leopard and Contributors
Creative Commons Attribution-Noncommercial 4.0 International
2014
Contents
Contents 1
1 Introduction 5
2 So, what is Chef? 6
2.1 What are the core principles? . . . . . . . . . . . . . . . . . . . 7
Idempotence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Thick Clients, Thin Server . . . . . . . . . . . . . . . . . . . . . 7
Order Matters . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Why you should use Chef? . . . . . . . . . . . . . . . . . . . . . 7
2.3 What doesn’t Chef do? . . . . . . . . . . . . . . . . . . . . . . . 8
2.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3 Chef Solo 10
3.1 Required software . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Rubygems and bundler . . . . . . . . . . . . . . . . . . . . . . . 11
Knife-solo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Berkshelf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 Creation of kitchen (chef-repo) . . . . . . . . . . . . . . . . . . . 12
3.3 .Chef folder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.4 Vendor cookbooks and berkshelf . . . . . . . . . . . . . . . . . . 13
3.5 Defining nodes . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.6 Vagrant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.7 Idempotence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.8 Defining roles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.9 Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.10 Defining environments . . . . . . . . . . . . . . . . . . . . . . . 23
3.11 Defining data bags . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.12 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4 Chef Server 30
1
Contents
4.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.2 Knife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.3 Bootstrap first node . . . . . . . . . . . . . . . . . . . . . . . . 36
Node in Vagrant . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.4 Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Attribute Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Automatic (Ohai) . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Attribute Precedence . . . . . . . . . . . . . . . . . . . . . . . . 41
4.5 Role . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.6 Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.7 Knife ssh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Chef-client cookbook . . . . . . . . . . . . . . . . . . . . . . . . 47
4.8 Data bags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5 Writing Cookbooks 51
5.1 Cookbook file organization . . . . . . . . . . . . . . . . . . . . . 51
5.2 Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.3 Resources and Providers . . . . . . . . . . . . . . . . . . . . . . 54
Bash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Cron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Directory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Cookbook_file . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Deploy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.4 Recipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Assign Dependencies . . . . . . . . . . . . . . . . . . . . . . . . 60
Create Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Include Recipes . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Reload Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Accessor Methods . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5.5 Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5.6 Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
File Specificity . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Partial Templates . . . . . . . . . . . . . . . . . . . . . . . . . . 68
5.7 LWRPs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Providers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
DSL Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Using LWRP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
2
Contents
5.8 HWRPs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
HWRPs and LWRPS . . . . . . . . . . . . . . . . . . . . . . . . 81
Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.9 Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.10 Ohai . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.11 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6 Testing Cookbooks 97
6.1 Test Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Unit Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . 97
Acceptance Testing . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.2 ChefSpec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Installing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.3 Fauxhai . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
6.4 Test Kitchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Installing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Running Kitchen Converge . . . . . . . . . . . . . . . . . . . . . 107
Manually Verifying . . . . . . . . . . . . . . . . . . . . . . . . . 108
Writing a Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Running Kitchen Test . . . . . . . . . . . . . . . . . . . . . . . 110
Adding a Platform . . . . . . . . . . . . . . . . . . . . . . . . . 111
Fixing Converge . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Adding a Suite . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Writing a Server Test . . . . . . . . . . . . . . . . . . . . . . . . 117
6.5 Chef Zero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Installing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Using with ChefSpec . . . . . . . . . . . . . . . . . . . . . . . . 122
Using with Test Kitchen . . . . . . . . . . . . . . . . . . . . . . 128
6.6 Minitest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Test Kitchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Minitest Chef Handler . . . . . . . . . . . . . . . . . . . . . . . 133
6.7 Cucumber . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.8 Static Analysis and Linting Tools . . . . . . . . . . . . . . . . . 138
Foodcritic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
RuboCop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Strainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
6.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
7 Tips and Tricks 146
3
Contents
7.1 Wrapper cookbook . . . . . . . . . . . . . . . . . . . . . . . . . 146
Codifying Standards in Your Organization . . . . . . . . . . . . 146
Modifying Upstream Cookbook Behavior . . . . . . . . . . . . . 146
Advanced Upstream Cookbook Modification . . . . . . . . . . . 147
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
7.2 Knife Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
7.3 Chef Metal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.4 Chef Sugar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
7.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Bibliography 157
4
1
Introduction
Chef is a configuration management and automation platform from Chef.
Chef helps you describe your infrastructure with code. Because your infras-
tructure is managed with code, it can be automated, tested and reproduced
with ease.
5
2
So, what is Chef?
Chef is a configuration management tool written in Ruby and Erlang. It
uses a pure-Ruby, domain-specific language (DSL) for writing system configu-
ration «recipes». Chef is used to streamline the task of configuring and main-
taining a company’s servers, and can integrate with cloud-based platforms such
as Rackspace and Amazon EC2 to automatically provision and configure new
machines.
The user writes «recipes» that describe how Chef manages server applica-
tions (such as Apache, MySQL, or Hadoop) and how they are to be configured.
These recipes describe a series of resources that should be in a particular state:
packages that should be installed, services that should be running, or files that
should be written. Chef makes sure each resource is properly configured and
corrects any resources that are not in the desired state.
Traditionally, Chef is used to manage GNU/Linux but later versions sup-
port running on Windows as well.
6
2.1. What are the core principles?
2.1 What are the core principles?
Idempotence
A recipe can run multiple times on the same system and the results will
always be identical. A resource is defined in a recipe, which then defines the
actions to be performed on the system. The chef-client ensures that actions
are not performed if the resources have not changed and that any action that
is performed is done the same way each time. If a recipe is re-run and nothing
has changed, then the chef-client will not do anything.
Thick Clients, Thin Server
Chef does as much work as possible on the node and as little as possible on
the server. A chef-client runs on each node and it only interacts with the server
when it needs to. The server is designed to distribute the data to each node
easily, including all cookbooks, recipes, templates, files, and so on. The server
also retains a copy of the state of the node at the conclusion of every chef-
client run. This approach ensures that the actual work needed to configure
each node in your infrastructure is distributed across the organization, rather
than centralized on smaller set of configuration management servers.
Order Matters
When the chef-client configures each node in the system, the order in which
that configuration occurs is very important. For example, if Apache is not
installed, then it cannot be configured and the daemon cannot be started.
Configuration management tools have struggled with this problem for a long
time. For each node a list of recipes is applied. Within a recipe, resources
are applied in the order in which they are listed. At any point in a recipe
other recipes may be included, which assures that all resources are applied.
The chef-client will never apply the same recipe twice. Dependencies are only
applied at the recipe level (and never the resource level). This means that
dependencies can be tracked between high-level concepts like «I need to install
Apache before I can start my Django application!» It also means that given
the same set of cookbooks, the chef-client will always execute resources in the
same exact order.
2.2 Why you should use Chef?
There are several reasons for using Chef:
Efficiency: It’s more effective to use Chef, which will contain all your
servers configuration in one place
7
2.3. What doesn’t Chef do?
Scalability: Do you need to scale your app? Split your server into several
cloud servers, by using environments, roles and nodes
Reusing and Save money: No need to install the same software 10 times
for your application on the server. Just create a new node in Chef and
after several minutes you will have configured the instance
Documentation: Chef is also documentation for your cloud, because Chef
recipes contain all the configuration information for your environment
And of course the main point is shown on picture 2.1.
Figure 2.1: Automate All The Things!
2.3 What doesn’t Chef do?
«Magically» configure your server
Blindly reuse cookbooks and recipes
Monitor your servers or software
Undoing concept
8
2.4. Summary
2.4 Summary
The key underlying principle of Chef is that you (the user) knows best
about what your environment is, what it should do, and how it should be
maintained. The chef-client is designed to not make assumptions about any
of those things. Only the individuals on the ground that’s you and your
team—understand the technical problems and what is required to solve them.
Only your team can understand the human problems (skill levels, audit trails,
and other internal issues) that are unique to your organization and whether
any single technical solution is viable.
The idea that you know best about what should happen in your organi-
zation goes hand-in-hand with the notion that you still need help keeping it
all running. It is rare that a single individual knows everything about a very
complex problem, let alone knows all of the steps that may be required to
solve them. The same is true with tools. Chef provides help with infrastruc-
ture management, and can help solve very complicated problems. Chef also
has a large community of users who have a lot of experience solving a lot of
very complex problems. That community can provide knowledge and support
in areas that your organization may not have and (along with Chef) can help
your organization solve any complex problem.
9
3
Chef Solo
Chef Solo is a simple way to begin working with Chef. It is an open source
version of the chef-client that allows using cookbooks with nodes without re-
quiring access to a server. Chef Solo runs locally and requires that a cookbook
(and any of its dependencies) be on the same physical disk as the node. It
is a limited-functionality version of the chef-client and does not support the
following:
Node data storage
Search indexes
Centralized distribution of cookbooks
A centralized API that interacts with and integrates infrastructure com-
ponents
Authentication or authorization
Persistent attributes
We will learn Chef Solo by practical examples in this chapter.
3.1 Required software
To get started working with Chef, you need to install the required software:
Virtual Box to provide a local virtual machine to manage using Chef
Vagrant to give a command line interface to manage Virtual Box
Git to revision Chef code and Recipes
Ruby (since Chef runs on it). For simple installation of it you can use
the RVM or Rbenv
To get started with Chef, we need to create chef-repo (kitchen), which will
contain all the data for a chef client(s). For example, I’ll create a directory
«my-cloud», which will contain a chef-repo (kitchen). In this directory I create
a file «Gemfile» with such content:
10
3.1. Required software
Download my-cloud/Gemfile
Line 1 s ource " h t tps : / / rubygems . org "
-
- gem knife s o l o
- gem b e r k s h e l f
and run command bundle in terminal. As a result, by using bundler, we
install any required rubygems.
Rubygems and bundler
RubyGems is a package manager for the Ruby programming language that
provides a standard format for distributing Ruby programs and libraries (in
a self-contained format called a «gem»), a tool designed to easily manage the
installation of gems, and a server for distributing them. It is analogous to
EasyInstall for the Python programming language.
Bundler provides a consistent environment for Ruby projects by tracking
and installing the exact gems and versions that are needed. Bundler is an
exit from dependency hell, and ensures that the gems you need are present in
development, staging, and production.
But why do we need these rubygems?
Knife-solo
Knife is a command-line tool that provides an interface between a local
chef-repo and the Chef server. But for Chef Solo is better to install the knife-
solo plugin. It will install knife as dependency and adds 5 subcommands to
knife tool:
knife solo init is used to create a new directory structure (i.e. kitchen)
that fits with Chef’s standard structure and can be used to build and
store recipes
knife solo prepare installs Chef on a given host. It’s structured to auto-
detect the target OS and change the installation process accordingly
knife solo cook uploads the current kitchen to the target host and runs
chef-solo on that host
knife solo bootstrap combines the two previous ones (prepare and cook)
knife solo clean removes the uploaded kitchen from the target host
Knife-solo also integrates with Berkshelf and Librarian-Chef.
Berkshelf
Berkshelf is used as well as the bundler for rubygems - it manages cook-
books and their dependencies. Also, there is librarian-chef, which performs a
11
3.2. Creation of kitchen (chef-repo)
similar function. I prefer to use berkshelf, because it has more features and
integrations.
3.2 Creation of kitchen (chef-repo)
Working with Chef Solo starts with creating kitchen (chef-repo). The
kitchen is located on a workstation (the location from which most users will
do most of their work, ie. your computer) and should be synchronized with a
version control system. To create a kitchen use knife-solo rubygems:
Download my-cloud
Line 1 $ cd myc lou d
- $ k n i f e s o l o i n i t .
- WARNING: No k n i f e c o n f i g u r a t i o n f i l e found
- C rea tin g k i t c h e n . . .
5 C rea tin g k n i f e . rb i n k i tchen . . .
- C rea tin g cupboards . . .
- S e t t i n g up B e r k s h e l f . . .
- $ l s o
- t o t a l 32
10 rwrr 1 l e o 14 Dec 14 0 0 :36 B e r k s f i l e
- rwrr@ 1 l e o 63 Dec 14 0 0:36 Ge mfile
- rwrr 1 l e o 4427 Dec 14 0 0 :36 Gemfile . l o c k
- drwxrxrx 3 l e o 102 Dec 14 00: 3 6 cookbooks
- drwxrxrx 3 l e o 102 Dec 14 00: 3 6 data_bags
15 drwxrxrx 3 l e o 102 Dec 14 00: 3 6 environments
- drwxrxrx 3 l e o 102 Dec 14 00: 3 6 nodes
- drwxrxrx 3 l e o 102 Dec 14 00: 3 6 r o l e s
- drwxrxrx 3 l e o 102 Dec 14 00: 3 6 s i t e cookbooks
Let’s consider the directory structure:
. chef - a hidden directory that is used to store .pem files and the knife.rb
file
cookbooks - directory for Chef cookbooks. This directory will be used for
vendor cookbooks, that will be installed with the help of berkshelf
data_bags - directory for Chef Data Bags
environments - directory for Chef environments
nodes - directory for Chef nodes
roles - directory for Chef roles
site cookbooks - directory for your custom Chef cookbooks
Berksfile - file contains a list of sources identifying which cookbooks to
retrieve and where to get them for berkshelf (like Gemfile for rubygems)
«Cookbooks» directory added into «.gitignore», because it contains only
vendor cookbooks. Vendor cookbook data will not be modified, so there is no
reason to keep them in VCS (git, mercurial, etc).
12
3.3. .Chef folder
3.3 .Chef folder
The «.chef» directory that is used to store .pem files and the knife.rb file.
For Chef Solo in this directory mostly contained only knife.rb file. A
knife.rb file is used to specify the chef-repo-specific configuration details for
Knife. This file is the default configuration file and is loaded every time this
executable is run. The configuration file is located at: ~/.chef/knife .rb. If a
knife.rb file is present in the . chef/knife .rb directory in the chef-repo, the set-
tings contained within that file will override the default configuration settings.
In example, knife.rb have such content:
Download my-cloud/.chef/knife.rb
Line 1 cookbook_path [ " cookbooks " , " s i t e cookbooks " ]
- node_path " nodes "
- role _path " r o l e s "
- environment_path " environments "
5 data_bag_path " data_bags "
- #encrypted_data_bag_secret " data_bag_key "
-
- k n i f e [ : b erk s helf _pat h ] = " cookbooks "
Let’s look at the meaning of these options:
cookbook_path - the sub-directory for cookbooks on the chef-client
node_path - the sub-directory for nodes on the chef-client
role_path - the sub-directory for roles on the chef-client
environment_path - the sub-directory for environments on the chef-client
data_bag_path - the sub-directory for Data Bags on the chef-client
knife [: berkshelf_path] - as you remember, knife-solo have integration with
Berkshelf and Librarian-Chef. By this option we set directory in which
knife will install vendor cookbooks from Berksfile before «cook» node
3.4 Vendor cookbooks and berkshelf
Suppose, that our task is to install Apache2 on node. For this purpose we
can use vendor cookbook through berkshelf. Huge amount of cookbooks you
can find at Chef community website. To install Apache2 cookbook we need
add it to Berksfile:
Download my-cloud/Berksfile
Line 1 s ource " http :/ / api . b e r k s h e l f . com "
-
- cookbook apache2
After running the command berks install this cookbook will be installed
with dependencies.
13
3.5. Defining nodes
Line 1 $ b erk s i n s t a l l
- Using apache2 ( 1 . 7 . 0 )
By default, you will not find this cookbook in «cookbooks» directory. Berk-
shelf installs cookbooks in « /.berkshelf» directory (to avoid duplication of
cookbooks, if you have several chef-repo). To install it in a special path you
can use vendor command:
Line 1 $ b erk s vendor cookbooks
- Using apache2 ( 1 . 7 . 0 )
- $ l s cookbooks
- apache2
Option knife [: berkshelf_path] in knife.rb file set to install our cookbooks into
cookbooks directory. In this case no need to run berks install −−path cookbooks
each time, when you need «cook» a node - knife-solo will do it automatically.
Now we can start to work with a nodes.
3.5 Defining nodes
Node itself represent any physical, virtual, or cloud machine. In most cases
number of nodes in kitchen equals to the number of machines in your cloud.
To install apache2 to the correct server, we need to create a file in the nodes
folder. Basically, this file is named as machine domain. For example, I attach
to the machine domain «web1.example.com» and create node for it:
Download my-cloud/nodes/web1.example.com.json
Line 1 {
- " r u n _ l i s t " : [ ]
- }
Node file should contain valid JSON document. Main key in this json is
run_list. This key contains array of recipes and roles, which should be executed
on machine. It always executed in the same order as listed in this key. As you
can see right now run_list is empty. To install apache2 you need to add the
recipe in this array. This information can be found in README of cookbook
(if this vendor cookbook well written), in metadata.rb or just open directory
recipes - the name of the file will mean recipe name. default .rb file means a
recipe that will be executed when you call the cookbook in run_list without
designation of recipe. Examples:
Download my-cloud/nodes/web1.example.com.json
Line 1 {
- " r u n _ l i s t " : [
- " r e c i p e [ apache 2 ] "
- ]
5 }
14
3.6. Vagrant
This run_list will execute default recipe from apache2 cookbook.
Now we are ready to test our kitchen.
3.6 Vagrant
For testing our chef kitchen in most cases we are using Vagrant. So what
is Vagrant?
Vagrant is free and open-source software for creating and configuring virtual
development environments. It can be considered a wrapper around VirtualBox
and configuration management software such as Chef. Since version 1.1, Va-
grant is no longer tied to VirtualBox and also works with other virtualization
software such as VMware and Amazon EC2.
Instead of building a virtual machine from scratch, which would be a slow
and tedious process, Vagrant uses a base image to quickly clone a virtual
machine. These base images are known as boxes in Vagrant, and specifying
the box to use for your Vagrant environment is always the first step after
creating a new Vagrantfile. For testing will be used Ubuntu 12.04 LTS 64-bit
(precise64 box).
Line 1 $ va gr ant box add p r e c i s e 6 4 ht tp : / / f i l e s . vagrantup . com/ p r e c i s e 6 4 .
box
More boxes you can find on Vagrantbox.es or Vagrant Cloud.
The first step for any project to use Vagrant is to configure Vagrant using
a Vagrantfile. We should execute vagrant init precise64 inside kitchen directory:
Line 1 $ va gr ant i n i t p r e c i s e 6 4
- A V a g r a n t f i l e has been pla c ed i n t h i s d i r e c t o r y . You ar e now
- ready to vagran t up your f i r s t v i r t u a l environment ! P l ease r ead
- the comments in the V a g r a n t f i l e as w e l l as documentation on
5 vagrantup . com f o r more information on u s ing Vagrant .
By default, Vagrantfile have such content:
Download my-cloud/nodes/Vagrantfile
Line 1 # * mode : ruby *
- # v i : s e t f t=ruby :
-
- # V a g r a n t f i l e API/ syntax v e r s i o n . Don t touch u n l e s s you know what
you r e do ing !
5 VAGRANTFILE_API_VERSION = " 2 "
-
- Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
- c o n f i g .vm. box = " p r e c i s e 6 4 "
- end
We can check, that vagrant is working fine by command «vagrant up»:
Line 1 $ va gr ant up
- B rin g ing machine d e f a u l t up with v i r t u a l b o x p r o v i d e r . . .
15
3.6. Vagrant
- [ d e f a u l t ] Importi ng base box p r e c i s e 6 4 . . .
- [ d e f a u l t ] Matching MAC a ddre s s fo r NAT networ king . . .
5 [ d e f a u l t ] S e t t i n g the name o f the VM. . .
- [ d e f a u l t ] C l e a r i n g any p r e v i o u s l y s e t fo rwarded p o r t s . . .
- [ d e f a u l t ] Cre ati ng sh ar ed f o l d e r s metadata . . .
- [ d e f a u l t ] C l e a r i n g any p r e v i o u s l y s e t network i n t e r f a c e s . . .
- [ d e f a u l t ] Prepa ring network i n t e r f a c e s based on c o n f i g u r a t i o n . . .
10 [ d e f a u l t ] Forwarding p o r t s . . .
- [ d e f a u l t ] 22 => 2222 ( adapter 1)
- [ d e f a u l t ] Booting VM. . .
- [ d e f a u l t ] Waiting f o r machine to boot . This may take a few minutes
. . .
- [ d e f a u l t ] Machine booted and ready !
15 [ d e f a u l t ] Mounting shared f o l d e r s . . .
- [ d e f a u l t ] / v ag rant
After this you can use command vagrant ssh to SSH into a running Vagrant
machine and give you access to a shell. Command «vagrant halt» shuts down
the running machine Vagrant is managing. vagrant destroy command stops the
running machine Vagrant is managing and destroys all resources that were
created during the machine creation process.
Tell vagrant to output the ssh connection details by doing:
Line 1 $ va gr ant sshc o n f i g
- Host d e f a u l t
- HostName 1 2 7 . 0 . 0 . 1
- User vagran t
5 Port 2222
- UserKnownHostsFile / dev/ n u l l
- S tr ictHostKeyCheckin g no
- P assword Au thenticat ion no
- I d e n t i t y F i l e / . . / . va grant / machines / d e f a u l t / v i r t u a l b o x /
pr iv at e_ ke y
10 I d e n t i t i e s O n l y yes
- LogLevel FATAL
Vagrant 1.7 changed how it handled private ssh keys. Starting with 1.7,
Vagrant generates a new private key for each machine. Earlier versions used the
same key, which was in the default location of ~/.vagrant.d/insecure_private_key.
The examples in this book are from the newer version of Vagrant.
In most cases, your machine will not contain chef client. We should install
it before using our kitchen. As you remember, we have knife with command
prepare. Let use this command:
Line 1 $ k n i f e s o l o pre p are v a grant@ l o c a l host i . / . vagrant / machines/
d e f a u l t / v i r t u a l b o x / private_key p 2222 N web1 . example . com
- B oot str app ing Chef . . .
- −−20131227 19:12:56 http s : / /www. opscode . com/ c h e f / i n s t a l l . sh
- R e solv i ng www. opscode . com (www. o pscode . com) . . . 1 8 4 . 1 0 6 . 2 8 . 9 1
5 . . .
- I n s t a l l i n g Chef 1 1 . 8 . 2
- i n s t a l l i n g with dpkg . . .
- S e l e c t i n g p r e v i o u s l y u n s e l e c t e d package c h e f .
16
3.6. Vagrant
- ( Reading database . . . 51095 f i l e s and d i r e c t o r i e s c u r r e n t l y
i n s t a l l e d . )
10 Unpacking c h e f ( from . . . / chef_11 . 8 . 2 _amd64 . deb ) . . .
- S e t t i n g up c h e f ( 1 1 . 8 . 2 1 . ubuntu . 1 2 . 0 4 ) . . .
- Thank you f o r i n s t a l l i n g Chef !
We used some options for the knife prepare command. i option specifies
ssh key for machine, N option specifies node name (if it different from host
name) and p option specifies SSH port. In most cases, this port is 2222, but
if you running several machines from vagrant, it will be different. You can read
what port is used for SSH by machine from output of command vagrant up. All
this credentials used to login by ssh on the node and install chef client.
Basically, to run our kitchen on node we are using knife solo cook command:
Line 1 $ k n i f e s o l o cook vagra n t @ l o calhos t i . / . vagrant / machines / d e f a u l t
/ v i r t u a l b o x / private_key p 2222 N web1 . example . com
- Running Chef on l o c a l h o s t . . .
- Checking Chef v e r s i o n . . .
- I n s t a l l i n g B e r k s h e l f cookbooks to cookbooks . . .
5 Using apache2 ( 1 . 7 . 0 )
- Uploading the kitchen . . .
- . . .
- * s e r v i c e [ apache2 ] a c t i o n s t a r t ( up to date )
- Chef C l i e n t f i n i s h e d , 1 r e s o u r c e s updated
But in Vagrant we can use build in command vagrant provision. This com-
mand runs any configured provisioners against the running Vagrant managed
machine.
Vagrantfile inside use ruby syntax, so we can use Ruby to DRY our config.
We should install chef gem inside vagrant:
Line 1 $ va gr ant p l u g in i n s t a l l c h e f
- I n s t a l l i n g the c h e f p l u g i n . This can t ak e a few minutes . . .
- I n s t a l l e d the p l u g i n c h e f ( 1 1 . 8 . 2 ) !
After this we can use chef gem inside Vagrantfile config:
Download my-cloud/nodes/Vagrantfile
Line 1 # * mode : ruby *
- # v i : s e t f t=ruby :
-
- r e q u i r e c h e f
5 r e q u i r e j s o n
-
- Chef : : Config . f r o m _ f i l e ( F i l e . j o i n ( F i l e . dirname (__FILE__) , . c h e f ,
k n i f e . rb ) )
- va grant_j son = JSON . pars e ( Pathname (__FILE__) . dirname . j o i n ( nodes ,
(ENV[ ’NODE ] | | web1 . example . com . j s o n ) ) . read )
-
10 # V a g r a n t f i l e API/ synt ax v e r s i o n . Don t touch u n l e s s you know what
you r e do ing !
- VAGRANTFILE_API_VERSION = " 2 "
17
3.6. Vagrant
-
- Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
- c o n f i g .vm. box = " p r e c i s e 6 4 "
15
- c o n f i g .vm. p r o v i s i o n : c h e f _solo do | c h e f |
- c h e f . cookbooks_path = Chef : : Config [ : cookbook_path ]
- c h e f . r ole s_p ath = Chef : : Co nf ig [ : role_path ]
- c h e f . data_bags_path = Chef : : Config [ : data_bag_path ]
20
- c h e f . environments_path = Chef : : C on fi g [ : environment_path ]
- #c h e f . environment = ENV[ ENVIRONMENT ] | | development
-
- c h e f . r u n _ l i s t = va grant_j son . d e l e t e ( r u n _ l i s t )
25 c h e f . j s o n = va grant_j son
- end
- end
Now consider that we have added.
On lines 4-5 required chef and json gem. JSON gem is as part of Va-
grant and chef gem was installed by previous command vagrant plugin install
chef. Further, we load knife.rb file in chef config and parse json from node file
«web1.example.com.json». After these manipulations we will have Chef::Config
ruby hash with knife configuration and vagrant_json ruby hash with attributes
from node.
On lines 16-26 defined Chef Solo configuration for Vagrant (environment is
commented, because we don’t have it right now, but we will use it later). More
information about this you can find in vagrant website.
Because we changed the Vagrantfile configuration, we need to restart the
test node by using vagrant reload command:
Line 1 $ va gr ant r e l o a d
- [ d e f a u l t ] Attempting g r a c e f u l shutdown o f VM. . .
- . . .
- [ d e f a u l t ] / v ag rant
5 [ d e f a u l t ] /tmp/ vagrantche f 1/ chefs o l o 3/ r o l e s
- [ d e f a u l t ] /tmp/ vagrantche f 1/ chefs o l o 2/cookbooks
- [ d e f a u l t ] /tmp/ vagrantche f 1/ chefs o l o 1/cookbooks
- [ d e f a u l t ] /tmp/ vagrantche f 1/ chefs o l o 4/data_bags
- [ d e f a u l t ] /tmp/ vagrantche f 1/ chefs o l o 5/environments
10 $ vagrant p r o v i s i o n
- [ d e f a u l t ] Running p r o v i s i o n e r : c h e f _ solo . . .
- Generating c h e f JSON and up loa din g . . .
- Running chefs o l o . . .
- s t d i n : i s not a tty
15 INFO: Forking c h e f i n s t a n c e t o co nverg e . . .
- INFO: * * * Chef 1 1 . 8 . 2 * * *
- . . .
- INFO: s e r v i c e [ apache2 ] r e s t a r t e d
- INFO: Chef Run complete i n 1 8.115 479422 sec ond s
20 INFO: Running re p o r t h a ndlers
- INFO: Report h a n d l ers complete
18
3.7. Idempotence
To verify that the apache2 successfully installed into node, we can forward
the 80 apache2 port to our machine. To do this, we modify the Vagrantfile:
Download my-cloud/nodes/Vagrantfile
Line 1 . . .
-
- Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
- c o n f i g .vm. box = " p r e c i s e 6 4 "
5 c o n f i g .vm. network : forwarded_port , g uest : 8 0 , h ost : 8085 # <==
add p ort forw a rdin g
-
- . . .
And again reload node:
Line 1 $ va gr ant r e l o a d
- . . .
- [ d e f a u l t ] P rep aring network i n t e r f a c e s based on c o n f i g u r a t i o n . . .
- [ d e f a u l t ] Forwarding p o r t s . . .
5 [ d e f a u l t ] 22 => 2222 ( adapter 1)
- [ d e f a u l t ] 80 => 8085 ( adapter 1)
- . . .
Now in any of your browser you can open url http://localhost:8085/ and
see 404 page from apache2 server (because we only install it).
3.7 Idempotence
As you read from previous chapter, one of the main idea of Chef is idem-
potence. It means, that Chef can safely be run multiple times on the same
machine. Once you develop your configuration, your machines will apply the
configuration and Chef will only make any changes to the system if the system
state does not match the configured state.
For now we have machine which contain apache2 running inside it. Let’s
run vagrant provision again:
Line 1 $ va gr ant p r o v i s i o n
- [ d e f a u l t ] Running p r o v i s i o n e r : c h e f _ solo . . .
- Generating c h e f JSON and up loa din g . . .
- Running chefs o l o . . .
5 s t d i n : i s not a tty
- . . .
- INFO: Chef Run complete i n 1 .0922 790 21 sec ond s
- . . .
As you can see, chef client did nothing, because configuration of the server
the same as in chef kitchen (that is why execution time also so small).
19
3.8. Defining roles
3.8 Defining roles
Roles help to classify the same server group. For example, in your project
you can have web, queue and db servers. In this case you can create such type
of roles, which will include the same attributes and run_list for nodes. Let’s
look at an example.
For example, in our project we have a web application servers, load balancer
server and database server. First we will create roles «web»:
Download my-cloud/roles/web.json
Line 1 {
- " name " : " web " ,
- " d e s c r i p t i o n " : " The base r o l e f o r systems t hat s e r v e web s e r v e r "
,
- " chef_type " : " r o l e " ,
5 " j s o n _ c l a s s " : " Chef : : Role " ,
- " d e f a u l t _ a t t r i b u t e s " : {
- " apache " : {
- " l i s t e n _ p o r t s " : [ " 80 " , " 443 " ]
- }
10 } ,
- " r u n _ l i s t " : [
- " r e c i p e [ apache2 ] "
- ]
- }
Let’s consider current json structure:
name - a unique name of the role. In most cases the same as the name of
file without extension
description - a description of the functionality that is covered by role
chef_type - this should always be set to «role»
json_class - this should always be set to «Chef::Role»
default_attributes - a set of attributes that should be applied to all nodes,
assuming the node does not already have a value for the attribute. This is
useful for setting global defaults that can then be overridden for specific
nodes. If more than one role attempts to set a default value for the same
attribute, the last role applied will be the role to set the attribute value.
This attribute is optional
override_attributes - a set of attributes that should be applied to all nodes,
even if the node already has a value for an attribute. This is useful for
ensuring that certain attributes always have specific values. If more than
one role attempts to set an override value for the same attribute, the last
role applied will win. This attribute is optional
run_list - a list of recipes and/or roles that will be applied and the order
in which those recipes and/or roles will be applied
env_run_lists - a list of environments, each specifying a recipe or a role
that will be applied to that environment. This attribute is optional
20
3.8. Defining roles
To use this role, we can create new node «web2.example.com» with content:
Download my-cloud/nodes/web2.example.com.json
Line 1 {
- " r u n _ l i s t " : [
- " r o l e [ web ] "
- ]
5 }
And check that everything is working:
Line 1 $ NODE=web2 . example . com . j s o n v ag rant p r o v i s i o n
- [ d e f a u l t ] Running p r o v i s i o n e r : c h e f _ solo . . .
- Generating c h e f JSON and upload ing . . .
- Running chefs o l o . . .
5 s t d i n : i s not a tty
- INFO: Forking c h e f i n s t a n c e t o co nverg e . . .
- INFO: * * * Chef 1 1 . 8 . 2 * * *
- INFO: Chef c l i e n t p id : 1224
- INFO: S e t t i n g the r u n _ l i s t to [ " r o l e [ web ] " ] from JSON
10 INFO: Run L i s t i s [ r o l e [ web ] ]
- INFO: Run L i s t expands to [ apache2 ]
- . . .
- INFO: Chef Run complete i n 1 .4371 57496 sec ond s
- INFO: Running re p o r t h a ndlers
15 INFO: Report h a n d l ers complete
As you can see, role defined in run_list by command role and role command
replaced to run list of this role by chef client. This allows you to use several
roles with recipes in the same node. For example, if web and database role
should exists on the same node (example: staging environment), you can define
run_list in node in such way:
Download my-cloud/nodes/web2.example.com.json
Line 1 {
- " r u n _ l i s t " : [
- " r o l e [ web ] " ,
- " r o l e [ db ] "
5 ]
- }
BTW, role can contain in run list another roles. For example:
Download my-cloud/roles/test.json
Line 1 {
- " name " : " t e s t " ,
- " d e s c r i p t i o n " : " The t e s t r o l e , i t i s not used i n k i t c h e n " ,
- " chef_type " : " r o l e " ,
5 " j s o n _ c l a s s " : " Chef : : Role " ,
- " r u n _ l i s t " : [
21
3.9. Attributes
- " r o l e [ web ] " ,
- " r e c i p e [ p o s t g r e s q l ] "
- ]
10 }
Role can contain attributes inside default_attributes or override_attributes
keys. In our example, «web» role can contain general settings for http lis-
ten ports, timeout of web server, etc. So let’s look in more detail about the
use of attributes.
3.9 Attributes
An attribute is a specific detail about a node. Attributes are used by the
chef-client to understand:
The current state of the node
What the state of the node was at the end of the previous chef-client run
What the state of the node should be at the end of the current chef-client
run
In our example we install and configure by chef apache 2 web server. In
vendor cookbook «apache2» exists default attributes.
Line 1 # General s e t t i n g s
- d e f a u l t [ apache ] [ l i s t e n _ p o r t s ] = [ " 80 " ]
- d e f a u l t [ apache ] [ conta c t ] = " ops@example . com"
- d e f a u l t [ apache ] [ timeout ] = 300
5 d e f a u l t [ apache ] [ k e e p a l i v e ] = "On"
- d e f a u l t [ apache ] [ k e e p a l i v e r e q u e s t s ] = 100
- d e f a u l t [ apache ] [ k e e p a l i v e t i m e o u t ] = 5
- . . .
This attributes used, because we don’t override its by environment, role or
node. For example, we can add 443 port through our «web» role:
Line 1 {
- " name " : " web " ,
- " d e s c r i p t i o n " : " The base r o l e f o r systems t hat s e r v e web s e r v e r "
,
- " chef_type " : " r o l e " ,
5 " j s o n _ c l a s s " : " Chef : : Role " ,
- " d e f a u l t _ a t t r i b u t e s " : {
- " apache " : {
- " l i s t e n _ p o r t s " : [ " 80 " , " 443 " ]
- }
10 } ,
- " r u n _ l i s t " : [
- " r e c i p e [ apache 2 ] "
- ]
- }
Also we can set keepalivetimeout for apache in node attributes:
22
3.10. Defining environments
Line 1 {
- " apache " : {
- " k e e p a l i v e t i m e o u t " : 30
- } ,
5 " r u n _ l i s t " : [
- " r o l e [ web ] "
- ]
- }
If we set listen_ports in node attribute, then this will override role listen_ports
attribute:
Line 1 {
- " apache " : {
- " k e e p a l i v e t i m e o u t " : 30 ,
- " l i s t e n _ p o r t s " : [ " 80 " , " 443 " , " 8080 " ]
5 } ,
- " r u n _ l i s t " : [
- " r o l e [ web ] "
- ]
- }
But role can override attributes, that should be applied to all nodes, even
if the node already has a value for an attribute. This is useful for ensuring
that certain attributes always have specific values. All this attributes should
be written in override_attributes key (instead of default_attributes):
Line 1 {
- " name " : " web " ,
- " d e s c r i p t i o n " : " The base r o l e f o r systems t hat s e r v e web s e r v e r "
,
- " chef_type " : " r o l e " ,
5 " j s o n _ c l a s s " : " Chef : : Role " ,
- " o v e r r i d e _ a t t r i b u t e s " : {
- " apache " : {
- " l i s t e n _ p o r t s " : [ " 80 " , " 443 " ]
- }
10 } ,
- " r u n _ l i s t " : [
- " r e c i p e [ apache 2 ] "
- ]
- }
More info about attributes you can find on wiki page.
3.10 Defining environments
An environment is a way to map an organization’s real-life workflow to
what can be configured and managed when using server. Every organization
begins with a single environment called the _default environment, which cannot
be modified (or deleted). Additional environments can be created to reflect
each organization’s patterns and workflow. For example, creating production,
23
3.10. Defining environments
staging, testing, and development environments. Generally, an environment is
also associated with one (or more) cookbook versions.
We create for our example development environment:
Download my-cloud/environments/development.json
Line 1 {
- " name " : " development " ,
- " d e s c r i p t i o n " : " development environment " ,
- " chef_type " : " environment " ,
5 " j s o n _ c l a s s " : " Chef : : Environment " ,
- " d e f a u l t _ a t t r i b u t e s " : {
- " apache " : {
- " t imeout " : 120
- }
10 }
- }
Let’s consider a json structure:
name - a unique name of the environment
description - a description of the environment
chef_type - this should always be set to environment
json_class - this should always be set to Chef::Environment
default_attributes - a set of attributes that should be applied to all nodes,
assuming the node does not already have a value for the attribute. This is
useful for setting global defaults that can then be overridden for specific
nodes. This attribute is optional
override_attributes - a set of attributes that should be applied to all nodes,
even if the node already has a value for an attribute. This is useful
for ensuring that certain attributes always have specific values. This
attribute is optional
As you can see environment doesn’t have run_list, but it has attributes.
Attributes in most cases contain information, which specific for environment:
connection information to databases (hostname, port, etc.), cluster settings
for database or queue, etc.
Now we can activate this development environment in Vagrantfile:
Download my-cloud/Vagrantfile
Line 1 Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
- . . .
- c h e f . environments_path = Chef : : C on fi g [ : environment_path ]
- c h e f . environment = ENV[ ’ENVIRONMENT ] | | development
5 . . .
- end
and check how it works:
24
3.11. Defining data bags
Line 1 $ va gr ant p r o v i s i o n
- . . .
- [2013 1231T21 : 5 3 : 5 7 + 0 0 : 0 0 ] INFO : Chef Run complete in 1.1 05324 838
sec ond s
- [2013 1231T21 : 5 3 : 5 7 + 0 0 : 0 0 ] INFO : Running r e p o r t h a n d l e r s
5 [2013 1231T21 : 5 3 : 5 7 + 0 0 : 0 0 ] INFO : Report handler s complete
To cook server by knife with environment you should use E argument:
Line 1 $ k n i f e s o l o cook vagra n t @ l o calhos t i . / . vagrant / machines / d e f a u l t
/ v i r t u a l b o x / private_key p 2222 N web1 . example . com E
development
- Running Chef on l o c a l h o s t . . .
- Checking Chef v e r s i o n . . .
- . . .
5 * s e r v i c e [ apache2 ] a c t i o n s t a r t ( up to da te )
- Chef C l i e n t f i n i s h e d , 1 r e s o u r c e s updated
You can set environment for node by attribute environment:
Download my-cloud/nodes/web1.example.com.json
Line 1 {
- " environment " : " development " ,
- " r u n _ l i s t " : [
- " r e c i p e [ apache 2 ] "
5 ]
- }
and use command knife solo cook without E argument.
Once an environment exists on the server, a node can be associated with
that environment using the chef_environment method of «node» object in Ruby
(I’ll consider this in the next chapters).
As you can see in Chef Solo environment can be used only for setting at-
tributes. In Chef Server it has additional feature for locking cookbook versions,
which we will consider in «4.6 Environment» chapter.
3.11 Defining data bags
A data bag is a global variable that is stored as JSON data. The contents
of a data bag can vary, but they often include sensitive information (such as
database passwords).
Knife is not working with Chef Solo data bags, but we can use knife-
solo_data_bag rubygem. Just add this gem in Gemfile:
Download my-cloud/Gemfile
Line 1 s ource " h t tps : / / rubygems . or g "
-
- gem k n i f e s o l o
- gem k n i f e solo_data_bag
25
3.11. Defining data bags
5 gem b e r k s h e l f
and run command bundle to install it. After installation your knife should
have new commands to work with Chef Solo:
Line 1 $ k n i f e help | grep s o l o
- k n i f e s o l o cook [USER@]HOSTNAME [ JSON ] ( o p t i o n s )
- k n i f e s o l o i n i t DIRECTORY
- k n i f e s o l o pre pare [USER@]HOSTNAME [JSON] ( o p t i o n s )
5 k n i f e s o l o b o o tstrap [USER@]HOSTNAME [ JSON ] ( o p t i o n s )
- k n i f e s o l o c l e a n [USER@]HOSTNAME
- k n i f e s o l o cook [USER@]HOSTNAME [JSON] ( o p t i o n s )
- k n i f e s o l o i n i t DIRECTORY
- k n i f e s o l o prep are [USER@]HOSTNAME [JSON] ( o p t i o n s )
10 k n i f e s o l o data bag c r e a t e BAG [ITEM] ( o p t i o n s )
- k n i f e s o l o data bag e d i t BAG ITEM ( o p t i o n s )
- k n i f e s o l o data bag l i s t ( o p t i o n s )
- k n i f e s o l o data bag show BAG [ ITEM] ( o p t i o n s )
- k n i f e s o l o c l e a n [USER@]HOSTNAME
For beginning, let’s create a plain text data bag:
Line 1 $ EDITOR=vim k n i f e s o l o data bag c r e a t e p ass mysql
- Created data_bag_item [ mysql ]
EDITOR environment variable is used to set editor, which will open data
bag file. I add in this JSON password for database and save it. Now we can
see the result:
Line 1 $ k n i f e s o l o data bag show pas s
- mysql :
- i d : mysql
- password : s e c r e t
If you open data bag file, you will see this JSON:
Download my-cloud/data_bags/pass/mysql.json
Line 1 {
- " name " : " data_bag_item_pass_mysql " ,
- " chef_type " : " data_bag_item " ,
- " j s o n _ c l a s s " : " Chef : : DataBagItem " ,
5 " data_bag " : " pas s " ,
- " raw_data " : {
- " i d " : " mysql " ,
- " password " : " s e c r e t "
- }
10 }
Let’s consider a json structure:
name - a unique name of data bag
chef_type - this should always be set to data_bag_item
json_class - this should always be set to Chef::DataBagItem
data_bag - name of data bag
26
3.11. Defining data bags
raw_data - values of data bag
The contents of a data bag can be encrypted using shared secret encryption.
This allows a data bag to store confidential information (such as a database
password) or to be managed in a source control system (without plain-text
data appearing in revision history).
Encrypting a data bag requires a secret key. A secret key can be created
in any number of ways. For example, OpenSSL can be used to generate a
random number, which can then be used as the secret key:
Line 1 $ o p e n s s l rand base64 512 | t r d \ r \n > . c h e f /
encrypted_data_bag_secret
The tr command eliminates any trailing line feeds. Doing so avoids key cor-
ruption when transferring the file between platforms with different line endings.
A data bag can be encrypted using a Knife command similar to:
Line 1 $ EDITOR=vim k n i f e s o l o data bag c r e a t e passwords mysql s e c r e t
f i l e . c h e f / encrypted_data_bag_secret
- Created data_bag_item [ mysql ]
Editor is opened with such content:
Line 1 {
- " i d " : " mysql "
- }
And you can add your password and save it:
Line 1 {
- " i d " : " mysql " ,
- " password " : " s e c r e t "
- }
As a result we obtain the encrypted data bag:
Line 1 $ k n i f e s o l o data bag show passwords mysql
- id : mysql
- password :
- c i p h e r : aes 256 cbc
5 encrypted_data : ++RR0s5f3rypFO3+SZj25px9QHTFq7AN854F3XZotnQ=
-
- i v : fxvNqHFHHbCNKntW8bBJkg==
-
- ve r s i o n : 1
An encrypted data bag item can be decrypted with a Knife command
similar to:
Download my-cloud/Gemfile
Line 1 $ k n i f e s o l o data bag show passwords mysql s e c r e t f i l e . c h e f /
encrypted_data_bag_secret
- id : mysql
- password : s e c r e t
27
3.11. Defining data bags
You can set encrypted_data_bag_secret in knife.rb file:
Download my-cloud/.chef/knife.rb
Line 1 cookbook_path [ " cookbooks " , " s i t e cookbooks " ]
- node_path " nodes "
- role _path " r o l e s "
- environment_path " environments "
5 data_bag_path " data_bags "
- encrypted_data_bag_secret " . c h e f / encrypted_data_bag_secret "
-
- k n i f e [ : b erk s helf _pat h ] = " cookbooks "
and in this case no need to define secret file for knife data bag commands:
Line 1 $ k n i f e s o l o data bag show passwords mysql
- id : mysql
- password : s e c r e t
For Vagrant you should set encrypted_data_bag_secret_key_path:
Download my-cloud/Vagrantfile
Line 1 . . .
- Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
- . . .
- c h e f . encrypted_data_bag_secret_key_path = Chef : : Config [ :
encrypted_data_bag_secret ]
5 . . .
- end
The Recipe DSL provides access to data bags and data bag items with the
following methods: data_bag(’bag’), where bag is the name of the data bag and
data_bag_item(’bag’, ’item’), where bag is the name of the data bag and item is
the name of the data bag item. Examples:
Line 1 data_bag ( " p ass " )
- # => [ " mysql " ]
- item = data_bag_item ( " p ass " , " mysql " )
- item [ " password " ]
5 # => " s e c r e t "
A recipe can access encrypted data bag items as long as the recipe is running
on a node that has access to the shared-key that is required to decrypt the
data. A secret can be specified by using the Chef::EncryptedDataBagItem.load
method. For example:
Line 1 mysql_creds = Chef : : EncryptedDataBagItem . l oad ( " passwords " , " mysql "
, sec ret _ke y )
- mysql_creds [ " password " ]
- # => " s e c r e t "
where secret_key is the argument that specifies the location of the file that
contains the encryption key. An encryption key can be configured so that the
28
3.12. Summary
chef-client knows where to look using the Chef::Config[:encrypted_data_bag_secret
] method, which defaults to /etc/chef/encrypted_data_bag_secret. When the de-
fault location is used, the argument that specifies the secret key file location is
assumed to be the default and does not need to be explicitly specified in the
recipe. For example:
Line 1 mysql_creds = Chef : : EncryptedDataBagItem . l oad ( " passwords " , " mysql "
)
- mysql_creds [ " password " ]
- # => " s e c r e t "
3.12 Summary
Chef Solo is a most simple way to begin working with Chef. Also it is very
good choice, if your environment small (several servers) and you don’t need
setup or buy separate Chef Server. But if you have huge numbers of servers
or you don’t like limited functionality of Chef Solo, in this case you should
thinking to setup or buy own Chef Server.
29
4
Chef Server
The Chef Server acts as a hub for configuration data. The server stores
cookbooks, the policies that are applied to nodes, and metadata that describes
each registered node that is being managed by the chef-client. Nodes use the
chef-client to ask the server for configuration details, such as recipes, templates,
and file distributions. The chef-client then does as much of the configuration
work as possible on the nodes themselves (and not on the server). This scalable
approach distributes the configuration effort throughout the organization.
The diagram 4.1 shows the relationships between the various elements of
Chef, including the nodes, the server, and the workstations. These elements
work together to provide the chef-client the information and instruction that
it needs so that it can do its job.
We will learn Chef Server by practical examples in this chapter.
4.1 Installation
Exists several ways to install own Chef Server:
Go to www.getchef.com/chef/install, select the operating system, ver-
sion, and architecture of the server and install the downloaded package
on it. After installation you can configure server by command «sudo
chef-server-ctl reconfigure»
Use Chef Solo to install Chef Server
Of course, I prefer to use Chef Solo to install and configure Chef Server.
Chef Solo will help us quickly deploy Chef Server on a new server, if something
happens with it (crash file system of server, etc.). Do not forget to make a
backups of Chef Server (because compared with Chef Solo, Chef Server will be
the point of failure in your configuration management system).
Let’s create our folder, which will contain all our Chef kitchen:
Line 1 $ mkdir mys e rver c lou d
- $ cd mys erver c lou d
30
4.1. Installation
Figure 4.1: Chef Infrastructure
- $ c at Gemfile
- s ourc e " http s : / / rubygems . org "
5
- gem c he f
- gem b e r k s h e l f
- $ bundle
- $ g i t c l o n e http s : / / g ithub . com/ opscode / c hef repo . g i t .
10 # or you can use " k n i f e s o l o i n i t . " , i f you w i l l i n s t a l l k n i f e
s o l o
To install and configure Chef Server exists cookbook chef-server. Let’s add
this cookbook in Berkshelf:
Download my-server-cloud/Berkshelf
Line 1 s ource " http : / / a pi . b e r k s h e l f . com "
-
- cookbook c he f s e r v e r
31
4.1. Installation
After running the command «berks install» this cookbook will be installed
with dependencies.
Line 1 $ b erk s i n s t a l l
- I n s t a l l i n g c hef s e r v e r ( 2 . 0 . 1 ) from s i t e : ht tp : / / cookbooks .
opscode . com/ a pi /v1/ cookbooks
- $ ber ks i n s t a l l path cookbooks
- Using ch efs e r v e r ( 2 . 0 . 1 )
Now we should configure a Chef Solo node for our Chef Server. From
chapter «3.5 Defining nodes» you should know how to define a node.
Download my-server-cloud/nodes/chef-server.example.com.json
Line 1 {
- " fqdn " : " 10 . 33 . 33 . 33 " ,
- " chefs e r v e r " : {
- " api_fqdn " : " 10 . 33 . 33 . 33 " ,
5 " v e r s i o n " : " l a t e s t " ,
- " c o n f i g u r a t i o n " : {
- " n o t i f i c a t i o n _ e m a i l " : " notify@example . com " ,
- " chefs erver webui " : {
- " e nabl e " : t r u e
10 }
- }
- } ,
- " r u n _ l i s t " : [
- " r e c i p e [ chefs e r v e r ] "
15 ]
- }
By configuration key you can change settings for Chef Server. All available
setting, which can be redefined, you can find here. Our Chef Server by default
takes your systems FQDN as Chef Server url, what is why I set «fqdn» in node
IP 10.33.33.33, which will set to my server by Vagrant.
First, we should generate Vagrantfile:
Line 1 $ va gr ant i n i t p r e c i s e 6 4
- A V a g r a n t f i l e has been pla c ed i n t h i s d i r e c t o r y . You ar e now
- ready to va grant up your f i r s t v i r t u a l environment ! P lease read
- the comments in the V a g r a n t f i l e as w e l l as documentation on
5 vagrantup . com f o r more information on u s ing Vagrant .
Next we need modeling a cluster of machines by Vagrant. Right now we
need only chef server. Let’s modify Vagrantfile:
Download my-server-cloud/Vagrantfile
Line 1 # * mode : ruby *
- # v i : s e t f t=ruby :
-
- r e q u i r e c h e f
5 r e q u i r e j s o n
32
4.1. Installation
-
- Chef : : Config . f r o m _ f i l e ( F i l e . j o i n ( F i l e . dirname (__FILE__) , . c h e f ,
k n i f e . rb ) )
-
- c h ef_s erve r _jso n = JSON . pars e ( Pathname (__FILE__) . dirname . j o i n (
nodes , ch efs e r v e r . example . com . j s o n ) . read )
10
- # V a g r a n t f i l e API/ synt ax v e r s i o n . Don t touch u n l e s s you know what
you r e do ing !
- VAGRANTFILE_API_VERSION = " 2 "
-
- Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
15
- c o n f i g .vm. d e f i n e : chef_server do | c h e f _ s e rver |
- c h e f _ s e rver .vm . box = " p r e c i s e 6 4 "
- c h e f _ s e rver .vm . network " private_network " , i p : " 1 0 . 3 3 . 3 3 . 3 3 "
-
20 c h e f _ s e rver .vm . p r o v i s i o n : c h ef_sol o do | c h e f |
- c h e f . cookbooks_path = Chef : : Config [ : cookbook_path ]
- c h e f . r ole s_p ath = Chef : : Co nf ig [ : role_path ]
- c h e f . data_bags_path = Chef : : Config [ : data_bag_path ]
- c h e f . environments_path = Chef : : C on fi g [ : environment_path ]
25
- c h e f . r u n _ l i s t = c h ef_s erve r _js o n . d e l e t e ( r u n _ l i s t )
- c h e f . j s o n = c h ef_ s erve r _js o n
- end
- end
30
- end
You should have installed chef gem inside vagrant, as we did in chapter
«3.6 Vagrant» and install/update Chef Client inside server by command «knife
solo prepare».
Line 1 $ va gr ant p r o v i s i o n
- [ c h e f _ s e rver ] Running p r o v i s i o n e r : c h e f _ s olo . . .
- Generating c h e f JSON and upload ing . . .
- Running chefs o l o . . .
5 s t d i n : i s not a tty
- INFO: Forking c h e f i n s t a n c e t o co nverg e . . .
- INFO: * * * Chef 1 1 . 8 . 2 * * *
- INFO: Chef c l i e n t p id : 1831
- INFO: S e t t i n g the r u n _ l i s t to [ " r e c i p e [ chef s e r v e r ] " ] from JSON
10 INFO: Run L i s t i s [ r e c i p e [ che f s e r v e r ] ]
- INFO: Run L i s t expands to [ ch efs e r v e r ]
- . . .
We can check Chef Server web interface by https://10.33.33.33 and info
about libraries version by https://10.33.33.33/version url. It should looks like
on figure 4.2.
In most cases web interface gives information about your cloud, which you
can get from knife tool. That is why generally it is disabled by attribute
«chef-server-webui.enable = false».
33
4.2. Knife
Figure 4.2: Chef Server Versions
Next we should configure our knife to work with this Chef Server.
4.2 Knife
After installation of Chef Server with default settings, Chef will generate
pem keys, which will be used for knife and Chef clients for authentication with
server. We should copy its from our Chef Server to «.chef» directory in project:
Line 1 $ va gr ant ssh c h e f _server
- Welcome to Ubuntu 1 2 . 0 4 . 1 LTS (GNU/ Linux 3.2.0 23 g e n e r i c x86_64 )
-
- v a gra n t@p r ecis e64 : ~ $ sudo cp / e t c / chefs e r v e r / * . pem / v ag rant / . c h e f
/
On real (production) Chef Server you can use scp command.
34
4.2. Knife
Next we should create for knife configuration file. As you remember from
chapter «3.3 .Chef folder», we already have «.chef» folder with knife .rb config.
But for Chef server we should define additional params. We can use for this
configure command of knife:
Line 1 $ k n i f e c o n f i g u r e i
- Over write . . . / mys erver c lou d / . c h e f / k n i f e . rb ? (Y/N) y
- Plea s e e n t e r the c h e f s e r v e r URL: [ http s : / / macbookproleo : 4 4 3 ]
http s : / / 1 0 . 3 3 . 3 3 . 3 3
- Plea s e e n t e r a name f o r the new user : [ l e o ]
5 Plea s e e n t e r the e x i s t i n g admin name : [ admin ]
- Plea s e e n t e r the l o c a t i o n o f the e x i s t i n g admin s p r i v a t e key : [ /
e t c / chefs e r v e r /admin . pem ] . c h e f /admin . pem
- Plea s e e n t e r the v a l i d a t i o n c lie n tna m e : [ ch efv a l i d a t o r ]
- Plea s e e n t e r the l o c a t i o n o f the v a l i d a t i o n key : [ / e t c / ch efs e r v e r
/ chefv a l i d a t o r . pem ] . c h e f / c he f v a l i d a t o r . pem
- Plea s e e n t e r the path to a c h e f r e p o s i t o r y ( or l e a v e blank ) :
10 C rea tin g i n i t i a l API us e r . . .
- Plea s e e n t e r a password f o r the new user :
- Created user [ l e o ]
- Configuration f i l e writ t e n to . . . / mys e rver c lou d / . c h e f / k n i f e . rb
Now our file look like this:
Download my-server-cloud/.chef/knife.rb
Line 1 l o g _ l e v e l : i n f o
- l o g _ l o c a t i o n STDOUT
- node_name l e o
- clien t _ k e y . c h e f / l e o . pem
5 v ali dat ion_ cli ent _na me ch efv a l i d a t o r
- v a l idat i on_k e y . c h e f / c he f v a l i d a t o r . pem
- c h e f_ser v e r_ur l h ttps : / / 1 0 . 3 3 . 3 3 . 3 3
- syntax_check_cache_path . c h e f / syntax_check_cache
Let’s little modify it:
Download my-server-cloud/.chef/knife.rb
Line 1 curr e n t _dir = F i l e . dirname (__FILE__)
-
- l o g _ l e v e l : i n f o
- l o g _ l o c a t i o n STDOUT
5 node_name l e o
- clien t _ k e y "#{c u r rent _ d i r }/ l e o . pem"
- syntax_check_cache_path "#{curre n t _dir }/ syntax_check_cache "
- v ali dat ion_ cli ent _na me " che f v a l i d a t o r "
- v a l idat i on_k e y "#{c u r rent _ d ir }/ ch ef v a l i d a t o r . pem"
10 c h e f_ser v e r_ur l " h t tps : / / 1 0 . 3 3 . 3 3 . 3 3 "
- cookbook_path [ "#{c u r rent_ d i r } / . . / cookbooks " , "#{
curr e n t _dir } / . . / s i t e cookbooks " ]
- node_path "#{c u r rent _ d ir } / . . / nodes "
- role _path "#{c urren t _ dir } / . . / r o l e s "
35
4.3. Bootstrap first node
- data_bag_path "#{c urre n t _ dir } / . . / data_bags "
15 environment_path "#{c u r rent _ d ir } / . . / environments "
- #encrypted_data_bag_secret " data_bag_key "
-
- k n i f e [ : b erk s helf _pat h ] = " cookbooks "
Let’s consider an options (part of this options already considered in chapter
«3.3 .Chef folder»):
chef_server_url - the URL for the Chef Server
node_name - the name of the node. This is typically also the same name
as the computer from which Knife is run
client_key - the location of the file which contains the client key. This key
used to authenticate with Chef Server
validation_key - the location of the file which contains the key used when
a chef-client is registered with a server. A validation key is signed using
the validation_client_name for authentication. Default value is /etc/chef/
validation .pem
validation_client_name - the name of the server that–along with the
validation_key is used to determine whether a chef-client may register
with a server. The validation_client_name located in the server and client
configuration files must match
syntax_check_cache_path - all files in a cookbook must contain valid Ruby
syntax. Use this setting to specify the location in which Knife caches
information about files that have been checked for valid Ruby syntax
log_level - log level for knife
log_location - log location for knife
Now we check what knife can communicate with Chef server:
Line 1 $ k n i f e u s e r l i s t
- admin
- l e o
- $ k n i f e c l i e n t l i s t
5 che f v a l i d a t o r
- che f webui
As you can see we successfully get list of users and clients from our Chef
Server.
4.3 Bootstrap first node
Once the Chef Server workstation is configured, it can be used to install
Chef on one (or more) nodes across the organization using a Knife bootstrap
operation. The knife bootstrap command is used to SSH into the target machine,
and then do what is needed to allow the chef-client to run on the node. It will
install the chef-client executable (if necessary), generate keys, and register the
node with the Chef Server. The bootstrap operation requires the IP address
36
4.3. Bootstrap first node
or FQDN of the target system, the SSH credentials (username, password or
identity file) for an account that has root access to the node, and (if the
operating system is not Ubuntu, which is the default distribution used by
knife bootstrap) the operating system running on the target system.
First, let’s add new server in Vagrantfile:
Download my-server-cloud/Vagrantfile
Line 1 . . .
-
- Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
-
5 . . .
-
- c o n f i g .vm. d e f i n e : c h e f _ f i r s t _ c l i e n t do | c h e f _ c l i e n t |
- c h e f _ c l i e n t . vm. box = " p r e c i s e 6 4 "
- c h e f _ c l i e n t . vm. network " private_net work " , i p : " 1 0 . 3 3 . 3 3 . 3 4 "
10 end
-
- end
And reload vagrant servers:
Line 1 $ va gr ant h a l t c h e f_server
- [ c h e f _ s e rver ] Attempting g r a c e f u l shutdown o f VM. . .
- $ vagrant up
- B rin g ing machine c h ef_ serv er up with v i r t u albox p r o v i d e r . . .
5 B rin g ing machine c h e f _ c l i e n t up with v i r t u a l b o x p r o v i d e r . . .
- . . .
And now we can bootstrap node:
Line 1 $ k n i f e b o o t strap l o c a l h o s t x vagrant p 2200 i . / . vagrant /
machines / d e f a u l t / v i r t u a l b o x / private_key N f i r s t . example . com
sudo
- B oot str app ing Chef on l o c a l h o s t
- l o c a l h o s t −−20140105 16:01:33 http s : / /www. opscode . com/ c h e f /
i n s t a l l . sh
- . . .
5 l o c a l h o s t Chef C l i e n t f i n i s h e d , 0 r e s o u r c e s updated
- $ k n i f e node l i s t
- f i r s t . example . com
And we can check what node created on server:
Line 1 $ k n i f e node l i s t
- f i r s t . example . com
- $ k n i f e c l i e n t show f i r s t . example . com
- admin : f a l s e
5 chef_type : c l i e n t
- j s o n _ c l a s s : Chef : : ApiC lie nt
- name : f i r s t . example . com
- public_key : BEGIN PUBLIC KEY
- . . .
10 END PUBLIC KEY
37
4.3. Bootstrap first node
-
- v a l i d a t o r : f a l s e
Node in Vagrant
You can automate registration of node with your Chef Server in Vagrant.
Let’s add new node in Vagrantfile:
Download my-server-cloud/Vagrantfile
Line 1 . . .
-
- Vagrant . c o n f i g u r e (VAGRANTFILE_API_VERSION) do | c o n f i g |
- . . .
5 c o n f i g .vm. d e f i n e : c h ef_s e c ond_ c lien t do | c h e f _ c l i e n t |
- c h e f _ c l i e n t . vm. box = " p r e c i s e 6 4 "
- c h e f _ c l i e n t . vm. network " private_net work " , i p : " 1 0 . 3 3 . 3 3 . 3 5 "
- c h e f _ c l i e n t . vm. p r o v i s i o n : c h e f _ c l i e n t do | c h e f |
- c h e f . chef _ s erver _ u rl = Chef : : Config [ : chef_ s e rver _ u rl ]
10 c h e f . validation_key_path = Chef : : Config [ : vali d ation _ key ]
- c h e f . v ali dat i on_ cli ent _na me = Chef : : Con fi g [ :
val ida tio n_cl ien t_n ame ]
- c h e f . node_name = second . example . com
-
- c h e f . dele te_node = tru e
15 c h e f . d e l e t e _ c l i e n t = tru e
- end
- end
-
- end
As you can see, options for «chef_clien the same as we set in knife.rb.
After command vagrant up you can check what new node registered:
Line 1 $ k n i f e node l i s t
- f i r s t . example . com
- second . example . com
When you provision your Vagrant virtual machine with Chef server, it cre-
ates a new Chef node entry and Chef client entry on the Chef server, using the
hostname of the machine. After you tear down your guest machine, Vagrant
can be configured to do it automatically with the following settings:
Line 1 c h e f . delete_node = tru e
- c h e f . d e l e t e _ c l i e n t = tru e
If you don’t specify it or set it to false, you must explicitly delete these
entries from the Chef server before you provision a new one with Chef server.
For example, using Chef’s built-in knife tool:
Line 1 $ k n i f e node d e l e t e second . example . com
- $ k n i f e c l i e n t d e l e t e second . example . com
Example of vagrant output when destroy a node:
38
4.4. Attributes
Line 1 $ va gr ant d e s troy c hef_ s e cond _ clie n t
- Are you s u r e you want to d e stroy the c hef _se con d_c lient VM? [ y/N
] y
- [ c hef_ s econ d _cli e n t ] For cing shutdown o f VM. . .
- [ c hef_ s econ d _cli e n t ] D est roy ing VM and a s s o c i a t e d d r i v e s . . .
5 [ c hef_ s econ d _cli e n t ] Running cle anup t a s k s f o r c h e f _ c l i e n t
p r o v i s i o n e r . . .
- Deleti ng c l i e n t " second . example . com " from Chef s e r v e r . . .
- Deleti ng node " second . example . com" from Chef s e r v e r . . .
- $ k n i f e node l i s t
- f i r s t . example . com
10 $ k n i f e c l i e n t l i s t
- che f v a l i d a t o r
- che f webui
- f i r s t . example . com
As you can see node and client removed from Chef server automatically.
4.4 Attributes
An attribute is a specific detail about a node. Attributes are used by the
chef-client to understand:
The current state of the node
What the state of the node was at the end of the previous chef-client run
What the state of the node should be at the end of the current chef-client
run
As you read from previous sections of book, attributes can be defined in
node, roles and environments, but it’s also can be defined by cookbooks.
Attribute Types
Attribute types can be any of the following:
default - attribute is automatically reset at the start of every chef-client
run and has the lowest attribute precedence
force_default - attribute is used to ensure that an attribute defined in a
cookbook (by an attribute file or by a recipe) takes precedence over a
default attribute set by a role or an environment
normal - attribute is a setting that persists on the target system and is
never reset during a chef-client run. A normal attribute has a higher
attribute precedence than a default attribute
override - attribute is automatically reset at the start of every chef-client
run and has a higher attribute precedence than default, force_default,
and normal attributes. An override attribute is most often specified in a
recipe, but can be specified in an attribute file, for a role, and/or for an
environment
39
4.4. Attributes
force_override - attribute is used to ensure that an attribute defined in a
cookbook (by an attribute file or by a recipe) takes precedence over an
override attribute set by a role or an environment
automatic - attribute contains data that is identified by Ohai at the begin-
ning of every chef-client run. An automatic attribute cannot be modified
and always has the highest attribute precedence
At the beginning of a chef-client run, all default, override, and automatic
attributes are reset. The chef-client rebuilds them using data collected by Ohai
at the beginning of the chef-client run and by attributes that are defined in
cookbooks, roles, and environments. Normal attributes are never reset. All
attributes are then merged and applied to the node according to attribute
precedence. At the conclusion of the chef-client run, all default, override, and
automatic attributes disappear, leaving only a collection of normal attributes
that will persist until the next chef-client run.
Automatic (Ohai)
An automatic attribute is a specific detail about a node, such as an IP
address, a host name, a list of loaded kernel modules, and so on. Automatic
attributes are detected by Ohai and are then used by the chef-client to ensure
that these attribute are handled properly during every chef-client run. The
most commonly accessed automatic attributes are:
node[platform] - the platform on which a node is running. This attribute
helps determine which providers will be used
node[platform_version’] - the version of the platform. This attribute helps
determine which providers will be used
node[ipaddress ] - the IP address for a node. If the node has a default
route, this is the IPV4 address for the interface. If the node does not
have a default route, the value for this attribute should be nil. The IP
address for default route is the recommended default value
node[macaddress’] - the MAC address for a node, determined by the same
interface that detects the «node[’ipaddress’]»
node[fqdn] - the fully qualified domain name for a node. This is used as
the name of a node unless otherwise set
node[hostname’] - the host name for the nod
node[domain’] - the domain for the node
node[ recipes ] - a list of recipes associated with a node (and part of that
node’s run-list)
node[ roles ] - a list of roles associated with a node (and part of that
node’s run-list)
The list of automatic attributes that are collected by Ohai at the start of
each chef-client run vary from organization to organization, and will often vary
40
4.5. Role
between the various server types being configured and the platforms on which
those servers are run. All attributes collected by Ohai are unmodifiable by the
chef-client.
Attribute Precedence
Attribute types can be any of the following:
1. A default attribute located in a cookbook attribute file
2. A default attribute located in a recipe
3. A default attribute located in an environment
4. A default attribute located in role
5. A force_default attribute located in a cookbook attribute file
6. A force_default attribute located in a recipe
7. A normal attribute located in a cookbook attribute file
8. A normal attribute located in a recipe
9. An override attribute located in a cookbook attribute file
10. An override attribute located in a recipe
11. An override attribute located in a role
12. An override attribute located in an environment
13. A force_override attribute located in a cookbook attribute file
14. A force_override attribute located in a recipe
15. An automatic attribute identified by Ohai at the start of the chef-client
run
where the last attribute in the list is the one that is applied to the node.
Attribute precedence, viewed from the same perspective as the overview
diagram 4.3, where the numbers in the diagram match the order of attribute
precedence:
Attribute precedence 4.4, when viewed as a table:
4.5 Role
Role work in the same way, as in Chef Solo (chapter «3.8 Defining roles»).
For example, we will install on «first.example.com» nginx. First of all, we need
add nginx cookbook in Berksfile:
Download my-server-cloud/Berksfile
Line 1 s ource " http : / / a pi . b e r k s h e l f . com "
-
- cookbook c he f s e r v e r
- cookbook nginx
5 cookbook yum , ~> 3 . 0
After command berks install we will create «nginx» role:
41
4.5. Role
Figure 4.3: Attribute precedence
Figure 4.4: Attribute precedence
Download my-server-cloud/roles/nginx.json
Line 1 {
- " name " : " nginx " ,
- " d e s c r i p t i o n " : " The base r o l e f o r systems t hat s e r v e web s e r v e r "
,
- " chef_type " : " r o l e " ,
42
4.5. Role
5 " j s o n _ c l a s s " : " Chef : : Role " ,
- " d e f a u l t _ a t t r i b u t e s " : {
- " nginx " : {
- " ins tall _me tho d " : " s o u rce " ,
- " v e r s i o n " : " 1 . 6 . 0 " ,
10 " d e f a u l t _ s i t e_enabled " : t r u e ,
- " s o u r c e " : {
- " u r l " : " ht tp : // nginx . o rg / download / nginx1 . 6 . 0 . t a r . gz "
- } ,
- " w o r k e r _ r l i m i t _ n o f i l e " : 30000 ,
15 " wo rk er _connections " : 4000
- }
- } ,
- " r u n _ l i s t " : [
- " r e c i p e [ nginx ] "
20 ]
- }
And «first.example.com» node:
Download my-server-cloud/nodes/first.example.com.json
Line 1 {
- " name " : " f i r s t . example . com " ,
- " j s o n _ c l a s s " : " Chef : : Node " ,
- " chef_type " : " node " ,
5 " normal " : {
- " fqdn " : " 10 . 33 . 33 . 34 "
- } ,
- " d e f a u l t " : {} ,
- " o v e r r i d e " : {} ,
10 " r u n _ l i s t " : [
- " r o l e [ nginx ] "
- ]
- }
As you can see, node have different format, than Chef Solo. Keys normal,
default and override contain attributes. Difference of this attributes you can
read in chapter «4.4 Attributes». Now we should upload cookbooks, role and
node in Chef Server. For this we can use knife and berkshelf:
Line 1 // upload a l l cookbooks from path cookbooks and s i t e cookbooks
( use f o r c e i f cookbook f r o z e n ) .
- // But f o r vendor cookbooks b e f o r e you need exe c ute ber ks i n s t a l l
path cookbooks
- $ k n i f e cookbook upload a
- // upload s e l e c t e d cookbook
5 $ k n i f e cookbook upload nginx
- // or upload a l l cookbooks by ber k s
- $ ber ks upload
- // c r e a t e / update r o l e from f i l e
- $ k n i f e r o l e from f i l e r o l e s / nginx . j s o n
10 // update node from f i l e
- $ k n i f e node from f i l e nodes / f i r s t . example . com . j s o n
43
4.5. Role
Next we should use knife ssh command to do something on nodes. The knife
ssh subcommand is used to invoke SSH commands (in parallel) on a subset of
nodes within an organization, based on the results of a search query. Example:
Line 1 // exec u te on f i r s t . example . com c hef c l i e n t
- $ k n i f e s sh name : f i r s t . example . com sudo che f c l i e n t ’ i . / .
va grant / machines / d e f a u l t / v i r t u a l b o x / private_key x vagrant
- 1 0 . 3 3 . 3 3 . 3 4 S t a r t i n g Chef C lie n t , v e r s i o n 1 1 . 8 . 2
- 1 0 . 3 3 . 3 3 . 3 4 r e s o l v i n g cookbooks f o r run l i s t : [ " nginx : : s o u r c e " ]
5 . . .
- 1 0 . 3 3 . 3 3 . 3 4 Recipe : nginx : : so u rce
- 1 0 . 3 3 . 3 3 . 3 4 * s e r v i c e [ nginx ] a c t i o n n othing (up to date )
- 1 0 . 3 3 . 3 3 . 3 4 * s e r v i c e [ nginx ] a c t i o n r e l o a d
- 1 0 . 3 3 . 3 3 . 3 4 r e l o a d s e r v i c e s e r v i c e [ nginx ]
10 1 0 . 3 3 . 3 3 . 3 4
- 1 0 . 3 3 . 3 3 . 3 4 Chef C l i e n t f i n i s h e d , 4 r e s o u r c e s updated
Second time you run this command can cause errors like «Failed to connect
to». It is because FQDN set by Ohai in chef-client will not visible hostname
(in my example nodes have «precise64» hostname). In real cluster you will not
have such problems, because you will use real hostnames. But in our case we
can use Vagrant «chef_clien stuff. Let’s create node «second.example.com»
and upload it on server:
Download my-server-cloud/nodes/second.example.com.json
Line 1 {
- " name " : " second . example . com" ,
- " j s o n _ c l a s s " : " Chef : : Node " ,
- " chef_type " : " node " ,
5 " normal " : {
- " fqdn " : " 10 . 33 . 33 . 35 "
- } ,
- " d e f a u l t " : {} ,
- " o v e r r i d e " : {} ,
10 " r u n _ l i s t " : [
- " r o l e [ nginx ] "
- ]
- }
Line 1 $ k n i f e node from f i l e nodes / second . example . com . j s o n
Now you can just run vagrant provision for second node:
Line 1 $ va gr ant p r o v i s i o n c h ef_s e c ond_ c lien t
- [ c hef_ s econ d _cli e n t ] Running p r o v i s i o n e r : c h e f _ c l i e n t . . .
- C rea tin g f o l d e r to hold c l i e n t key . . .
- Uploading c h e f c l i e n t v a l i d a t i o n key . . .
5 Generating c h e f JSON and upload ing . . .
- [ c hef_ s econ d _cli e n t ] Warning : Chef run l i s t i s empty . This may not
be what you want .
- Running chef c l i e n t . . .
- s t d i n : i s not a tty
- INFO: Forking c h e f i n s t a n c e t o co nverg e . . .
44
4.6. Environment
10 INFO: * * * Chef 1 1 . 8 . 2 * * *
- INFO: Chef c l i e n t p id : 1198
- . . .
- INFO: Running re p o r t h a ndlers
- INFO: Report h a n d l ers complete
It will run chef-client on server, which will get all needed info how to
«cook» node from Chef Server. After this you can check what on url
http://10.33.33.35/ running nginx.
4.6 Environment
Environment in Chef Server is similar to Chef Solo (chapter «3.10 Defining
environments»). Except default attributes, environment in Chef Server can
contain cookbook_versions attribute. In this attribute you can lock cookbook
versions. Example:
Download my-server-cloud/environments/development.json
Line 1 {
- " name " : " development " ,
- " d e s c r i p t i o n " : " development environment " ,
- " chef_type " : " environment " ,
5 " j s o n _ c l a s s " : " Chef : : Environment " ,
- " d e f a u l t _ a t t r i b u t e s " : {} ,
- " co ok book_versions " : {
- " nginx " : "= 2 . 2 . 0 "
- }
10 }
As you can see we locked nginx to 2.2.0 version for development environ-
ment. This is very useful stuff, because when released new versions of some
cookbooks, you need check what you can update it to new version without
break a production environment. In this case you can change version of cook-
books inside cookbook_versions in your test (development, staging, etc.) envi-
ronment, check what all work fine with new cookbook and update production
environment only in success case.
Set environment to node similar to Chef Solo, but in this case you should
use chef_environment attribute. Example:
Download my-server-cloud/nodes/second.example.com.json
Line 1 {
- " name " : " second . example . com" ,
- " j s o n _ c l a s s " : " Chef : : Node " ,
- " chef_type " : " node " ,
5 " chef_environment " : " development " ,
- " normal " : {
- " fqdn " : " 10 . 33 . 33 . 35 "
45
4.7. Knife ssh
- } ,
- " d e f a u l t " : {} ,
10 " o v e r r i d e " : {} ,
- " r u n _ l i s t " : [
- " r o l e [ nginx ] "
- ]
- }
To check what all work fine, first of all you should upload you environment
and update node in Chef Server:
Line 1 $ k n i f e environment from f i l e environ ments / development . j s o n
- Updated Environment development
-
- $ k n i f e node from f i l e nodes / second . example . com . j s o n
5 Updated Node second . example . com !
And check what all work fine by command vagrant provision for second node:
Line 1 $ va gr ant p r o v i s i o n c h ef_s e c ond_ c lien t
- [ c hef_ s econ d _cli e n t ] Running p r o v i s i o n e r : c h e f _ c l i e n t . . .
- C rea tin g f o l d e r to hold c l i e n t key . . .
- Uploading c h e f c l i e n t v a l i d a t i o n key . . .
5 Generating c h e f JSON and upload ing . . .
- . . .
4.7 Knife ssh
The knife ssh subcommand is used to invoke SSH commands (in parallel)
on a subset of nodes within an organization, based on the results of a search
query. We already use it to run chef-client on «first.example.com» node. Let’s
consider an examples.
To find the uptime of all of web servers (all node, which have role «web»):
Line 1 $ k n i f e ssh " r o l e : web " " uptime " i . . / keys / p rodu ctio n . pem x
ubuntu
- * * * .com 1 3 : 1 8 : 2 8 up 55 days , 14 min , 1 us er , l oad a ver age :
0 . 0 0 , 0 . 0 1 , 0 .05
- * * * .com 1 3 : 1 8 : 2 8 up 75 days , 2 3 : 4 9 , 1 user , l oad a ver age :
0 . 0 0 , 0 . 0 1 , 0 .05
- * * * .com 1 3 : 1 8 : 2 8 up 55 days , 13 min , 1 us er , l oad a ver age :
0 . 0 8 , 0 . 0 3 , 0 .05
To run the chef-client on all nodes:
Line 1 $ k n i f e ssh name : * sudo chefc l i e n t i . . / k eys / p r odu c tio n . pem
x ubuntu
- . . .
To run the chef-client on all nodes, which name begin from «second» string:
Line 1 $ k n i f e ssh name : second * sudo c he f c l i e n t i . . / keys /
pro d uct ion . pem x ubuntu
- . . .
46
4.7. Knife ssh
To upgrade all nodes (don’t do this on real production nodes):
Line 1 $ k n i f e ssh name : * sudo a p t i t u d e upgrade y i . . / keys /
pro d uct ion . pem x ubuntu
- . . .
To get memory information from all nodes in staging environment:
Line 1 $ k n i f e ssh " chef_environment : s t a g i n g " " f r e e m" i . . / keys /
pro d uct ion . pem x ubuntu
- * * * .com t o t a l used f r e e sh ar ed
b u f f e r s cached
- * * * .com Mem: 1692 1182 509 0
181 491
- * * * .com /+ b u f f e r s / cache : 509 1183
5 * * * .com Swap : 895 6 889
- . . .
Chef-client cookbook
Sometimes you may want update you servers automatically (instead of
using knife ssh). For example, you just updated new cookbooks, roles and nodes
and all nodes should automatically fetch new cookbooks and apply changes if
something updated (and for you not critical update speed). We can use special
cookbook chef-client for this. It allows for use bluepill, daemontools, runit or
cron to configure your systems to run Chef Client as a service. First of all we
need add this cookbook in Berksfile and run berks install :
Download my-server-cloud/Berksfile
Line 1 s ource " http : / / a pi . b e r k s h e l f . com "
-
- cookbook c he f s e r v e r
- cookbook nginx
5 cookbook yum , ~> 3 . 0
- cookbook c he f c l i e n t
Next we will create new node:
Download my-server-cloud/roles/chef-client.json
Line 1 {
- " name " : " c hef c l i e n t " ,
- " d e s c r i p t i o n " : " The base r o l e f o r che f c l i e n t " ,
- " chef_type " : " r o l e " ,
5 " j s o n _ c l a s s " : " Chef : : Role " ,
- " d e f a u l t _ a t t r i b u t e s " : {
- " c h e f _ c l i e n t " : {
- " i n t e r v a l " : 1800 ,
- " i n i t _ s t y l e " : " u p s t a r t " ,
10 " c o n f i g " : {
- " c l i e n t _ f o r k " : t r u e
47
4.7. Knife ssh
- }
- }
- } ,
15 " r u n _ l i s t " : [
- " r e c i p e [ chef c l i e n t ] " ,
- " r e c i p e [ chef c l i e n t : : c o n f i g ] "
- ]
- }
We set by attributes to check Chef each 1800 sec (30 min) and use «fork»
method for execution of chef-client.
Next add chefclient in node run_list:
Download my-server-cloud/nodes/second.example.com.json
Line 1 {
- " name " : " second . example . com" ,
- " j s o n _ c l a s s " : " Chef : : Node " ,
- " chef_type " : " node " ,
5 " chef_environment " : " development " ,
- " normal " : {
- " fqdn " : " 10 . 33 . 33 . 35 "
- } ,
- " d e f a u l t " : {} ,
10 " o v e r r i d e " : {} ,
- " r u n _ l i s t " : [
- " r o l e [ chef c l i e n t ] " ,
- " r o l e [ nginx ] "
- ]
15 }
After upload coobooks, role and nodes on Chef Server, we can check what
chef-client will be added to upstart:
Line 1 $ va gr ant p r o v i s i o n c h ef_s e c ond_ c lien t
- [ c hef_ s econ d _cli e n t ] Running p r o v i s i o n e r : c h e f _ c l i e n t . . .
- C rea tin g f o l d e r to hold c l i e n t key . . .
- Uploading c h e f c l i e n t v a l i d a t i o n key . . .
5 . . .
- INFO: s e r v i c e [ chef c l i e n t ] r e s t a r t e d
- INFO: Chef Run complete i n 9 .5992 37616 sec ond s
- INFO: Running re p o r t h a ndlers
- INFO: Report h a n d l ers complete
By command knife status you can check chef-client status on nodes:
Line 1 $ k n i f e s t a t u s
- 3 minutes ago , f i r s t . example . com , p r e c i s e 6 4 , 1 0 . 0 . 2 . 1 4 , ubuntu
1 2 . 0 4 .
- 2 minutes ago , s econd . example . com , p r e c i s e 6 4 , 1 0 . 0 . 2 . 1 5 , ubuntu
1 2 . 0 4 .
Of course, you can filter this list, if you have too many nodes (filter by role,
environment, etc.):
48
4.8. Data bags
Line 1 $ k n i f e s t a t u s name : second *
- 4 minutes ago , s econd . example . com , p r e c i s e 6 4 , 1 0 . 0 . 2 . 1 5 , ubuntu
1 2 . 0 4 .
- $ k n i f e s t a t u s r o l e : nginx
- 6 minutes ago , f i r s t . example . com , p r e c i s e 6 4 , 1 0 . 0 . 2 . 1 4 , ubuntu
1 2 . 0 4 .
5 5 minutes ago , s econd . example . com , p r e c i s e 6 4 , 1 0 . 0 . 2 . 1 5 , ubuntu
1 2 . 0 4 .
4.8 Data bags
Data bags work similar as in Chef Solo (chapter «3.11 Defining data bags»).
But in your recipe you can use search command to search data bags on servers.
Any search for a data bag (or a data bag item) must specify the name of the
data bag and then provide the search query string that will be used during
the search. For example, to use Knife to search within a data bag named
«admin_data» across all items, except for the «admin_users» item, enter the
following:
Line 1 $ k n i f e s e a r c h admin_data " (NOT i d : admin_users ) "
Or, to include the same search query in a recipe, use a code block similar
to:
Line 1 s earch ( : admin_data , "NOT id : admin_users " )
It may not be possible to know which data bag items will be needed. It may
be necessary to load everything in a data bag (but not know what «everything»
is). Using a search query is the ideal way to deal with that ambiguity, yet still
ensure that all of the required data is returned. The following examples show
how a recipe can use a series of search queries to search within a data bag
named «admins». For example, to find every administrator:
Line 1 s earch ( : admins , " * : * " )
Or to search for an administrator named «charlie»:
Line 1 s earch ( : admins , " i d : c h a r l i e " )
Or to search for an administrator with a group identifier of «ops»:
Line 1 s earch ( : admins , " gid : ops " )
Or to search for an administrator whose name begins with the letter «c»:
Line 1 s earch ( : admins , " i d : c * " )
Data bag items that are returned by a search query can be used as if they
were a hash. For example:
Line 1 c h a r l i e = s e a r ch ( : admins , " i d : c h a r l i e " ) . f i r s t
- # => v a r i a b l e c h a r l i e i s s e t t o t he c h a r l i e data bag item
- c h a r l i e [ " g id " ]
- # => " ops "
5 c h a r l i e [ " s h e l l " ]
- # => "/ bin / zsh "
49
4.9. Summary
4.9 Summary
Chef is a systems and cloud infrastructure automation framework that
makes it easy to deploy servers and applications to any physical, virtual, or
cloud location, no matter the size of the infrastructure.
50
5
Writing Cookbooks
A cookbook is the fundamental unit of configuration and policy distribu-
tion. Each cookbook defines a scenario, such as everything needed to install
and configure MySQL, and then it contains all of the components that are
required to support that scenario.
As you read from previous chapter vendor cookbooks can help you to install
and configure any possible software, but in most cases it is not enough. This is
because you have your application, which need install, configure special cases
only for this application. That is why you must know how to write own Chef
cookbooks.
Chef cookbooks is written on Ruby language. It is dynamic and open source
programming language, which very well fits to use as DSL for Chef recipes.
Before you start reading this chapter, you should know Ruby at least basic
stuff (Ruby types, loops, conditions, ERB, etc).
5.1 Cookbook file organization
At the beginning we will generate cookbook by knife or berks. You can
install both by bundler (we already did this in our Chef server kitchens). So,
let’s create «my_cool_app» cookbook inside «site-cookbooks» dir:
Line 1 $ cd s i t e cookbooks
- $ k n i f e cookbook c r e a t e my_cool_app o .
- # or ano th er way by b erks
- $ ber ks cookbook my_cool_app
After this you should see inside «site-cookbooks» new folder
«my_cool_app». This is our cookbook, which have such file structure
inside:
Line 1 $ l s l my_cool_app
- t o t a l 72
- drwxrxrx .
- drwxrxrx . .
5 drwxrxrx . g i t
51
5.1. Cookbook file organization
- rwrr . g i t i g n o r e
- rwrr B e r k s f i l e
- rwrr Gemfil e
- rwrr@ LICENSE
10 rwrr README.md
- rwrr T h o r f i l e
- rwrr V a g r a n t f i l e
- drwxrxrx a t t r i b u t e s
- rwrr c h e f i g n o r e
15 drwxrxrx d e f i n i t i o n s
- drwxrxrx f i l e s
- drwxrxrx l i b r a r i e s
- rwrr metadata . rb
- drwxrxrx pr o v i d e r s
20 drwxrxrx r e c i p e s
- drwxrxrx r e s o u r c e s
- drwxrxrx tem p late s
Let’s consider this structure:
. git - git repository skeleton (no need to do «git init»)
. gitignore - specifies intentionally untracked files to ignore by Git
Berksfile - file with cookbook dependencies for berkshelf (used for testing)
Gemfile - file with gems for bundler (used for testing)
LICENSE - file contain license information about cookbook
README.md - file contains information about cookbook. «.md» mean
markdown syntax
Thorfile - file include tasks for thor gem (toolkit for building command-
line interfaces, used for testing)
Vagrantfile - file describe the type of machine required for a cookbook for
vagrant
attributes - this folder contains ruby files with default cookbook at-
tributes, which you can redefine in environment, role or node attributes
chefignore - specifies intentionally untracked files to ignore by Chef, Knife
definitions - this folder contains ruby files which is used to declare re-
sources so they can be added to the resource collection
files - this folder contains files, which just need transfer to node (used
by command cookbook_file)
libraries - this folder contains ruby files with Ruby code to be included in
a cookbook, either as a way to extend the classes used by the chef-client
or to implement a new class directly
metadata.rb - a file, which contain all information (metadata) about the
cookbook: name, dependencies, etc. (it is like gemset for ruby gems,
package.json for npm, etc)
providers - this folder contains providers for chef resources
recipes - this folder contains recipes of this cookbook
resources - this folder contains resources for chef (like build-in cron, deploy,
etc)
52
5.2. Metadata
templates - this folder contains files written in a markup language that al-
lows the contents of a file to be dynamically generated based on variables
or complex logic. Used an Embedded Ruby (ERB) templates.
5.2 Metadata
Metadata is a file, which contains all main information about cookbook.
Let’s consider our generated example:
Download my-server-cloud/site-cookbooks/my_cool_app/metadata.rb
Line 1 name my_cool_app
- m aint aine r YOUR_NAME
- maintainer_email ’YOUR_EMAIL
- l i c e n s e A l l r i g h t s r e s e r v e d
5 d e s c r i p t i o n I n s t a l l s / C o n f igures my_cool_app
- l o n g _ d e s c r i p t i o n IO . re ad ( F i l e . j o i n ( F i l e . dirname (__FILE__) , ’README
.md ) )
- v e r s i o n 0 . 1 . 0
This file written on Ruby and can have such settings:
name - the name of the cookbook
maintainer - the name of the person responsible for maintaining a cook-
book, either an individual or an organization
maintainer_email - the email address for the person responsible for main-
taining a cookbook. Only one email can be listed here
license - the type of license under which a cookbook is distributed:
«Apache v2.0», «GPL v2», «GPL v3», «MIT», or license «Proprietary -
All Rights Reserved» (default)
description - a short description of a cookbook and its functionality
long_description - a longer description that ideally contains full instruc-
tions on the proper use of a cookbook, including definitions, libraries,
dependencies, and so on. In the example the contents pulled from
«README.md» file
version - the current version of a cookbook. Version numbers always follow
a simple three-number version sequence
attribute - the list of attributes that are required to configure a cookbook
depends - indicates that a cookbook has a dependency on another cook-
book
recommends - adds a dependency on another cookbook that is recom-
mended, but not required
suggests - adds a dependency on another cookbook that is suggested, but
not required
conflicts - indicates that a cookbook conflicts with another cookbook or
cookbook version
53
5.3. Resources and Providers
grouping - adds a title and description to a group of attributes within a
namespace
provides - adds a recipe, definition, or resource that is provided by this
cookbook, should the auto-populated list be insufficient
recipe - a description for a recipe, mostly for cosmetic value within the
server user interface
replaces - indicates that this cookbook should replace another (and can
be used in-place of that cookbook)
supports - indicates that a cookbook has a supported platform
We need modify it to set our information about this cookbook:
Download my-server-cloud/site-cookbooks/my_cool_app/metadata.rb
Line 1 name my_cool_app
- m aint aine r Alexey V a s i l i e v
- maintainer_email leopard_ne@inbox . ru
- l i c e n s e MIT
5 d e s c r i p t i o n I n s t a l l s / C o n f igures my_cool_app
- l o n g _ d e s c r i p t i o n IO . re ad ( F i l e . j o i n ( F i l e . dirname (__FILE__) , ’README
.md ) )
- v e r s i o n 0 . 1 . 0
As of writing this cookbook, we will be adding information to this file on
it.
5.3 Resources and Providers
As you read from previous chapter, Chef inside have resources (in example
we used package resource). A resource defines the actions that can be taken,
such as when a package should be installed, whether a service should be enabled
or restarted, which groups, users, or groups of users should be created, where
to put a collection of files, what the name of a new directory should be, and
so on. During a chef-client run, each resource is identified and then associated
with a provider. The provider then does the work to complete the action
defined by the resource. Each resource is processed in the same order as they
appear in a recipe. The chef-client ensures that the same actions are taken the
same way everywhere and that actions produce the same result every time. A
resource is implemented within a recipe using Ruby.
Let’s look at the most necessary resources.
Bash
The bash resource is used to execute scripts using the Bash interpreter
and includes all of the actions and attributes that are available to the execute
resource. Example:
54
5.3. Resources and Providers
Line 1 bash " i n s t a l l _ s o m e t h i n g " do
- u s er " r oot "
- cwd " /tmp"
- code <<EOH
5 wget http : / /www. example . com/ t a r b a l l . t a r . gz
- t a r z x f t a r b a l l . t a r . gz
- cd t a r b a l l
- ./ c o n f i g u r e
- make
10 make i n s t a l l
- EOH
- end
Cron
The cron resource is used to manage cron entries for time-based job schedul-
ing. Attributes for a schedule will default to * if not provided. The cron
resource requires access to a crontab program, typically cron. Example:
Line 1 cron " name_of_cron_entry " do
- hour " 8 "
- weekday " 6 "
- mail t o " admin@opscode . com"
5 a c t i o n : cr e a t e
- end
Line 1 cron " noop " do
- hour " 5 "
- minute " 0 "
- command " / bin / true "
5 end
Directory
The directory resource is used to manage a directory, which is a hierarchy
of folders that comprises all of the information stored on a computer. The root
directory is the top-level, under which the rest of the directory is organized.
The directory resource uses the name attribute to specify the path to a location
in a directory. Typically, permission to access that location in the directory is
required. Example:
Line 1 d i r e c t o r y " /tmp/ something " do
- owner " r o ot "
- group " r oot "
- mode 00755
5 a c t i o n : cr e a t e
- end
Line 1 %w{ d i r 1 d i r 2 d i r 3 } . each do | d i r |
- d i r e c t o r y " /tmp/ mydirs/#{ d i r } " do
- mode 00775
55
5.3. Resources and Providers
- owner " r o o t "
5 group " r oot "
- a c t i o n : cr e a t e
- r e c u r s i v e t rue
- end
- end
Git
The git resource is used to manage source control resources that exist in
a git repository. git version 1.6.5 (or higher) is required to use all of the
functionality in the git resource. Example:
Line 1 g i t " / opt / mysources / couch " do
- r e p o s i t o r y " g i t : / / g i t . apache . org / couchdb . g i t "
- r e f e r e n c e " master "
- a c t i o n : sync
5 end
Line 1 g i t "#{Chef : : C on fi g [ : fil e_c ach e_p ath ] } / rubyb u i l d " do
- r e p o s i t o r y " g i t : / / githu b . com/ s step h ens o n / rubyb u i l d . g i t "
- r e f e r e n c e " master "
- a c t i o n : sync
5 end
-
- bash " i n s t all_ruby _ b u i l d " do
- cwd "#{Chef : : Config [ : fi le_ cac he_ pat h ] } / ruby b u i l d "
- user " rbenv "
10 group " rbenv "
- code <<EOH
- . / i n s t a l l . sh
- EOH
- environment PREFIX => " / usr / l o c a l "
15 end
Link
The link resource is used to create symbolic or hard links. Example:
Line 1 l i n k " /tmp/ passwd " do
- to " / et c /passwd "
- end
Line 1 l i n k " /tmp/ passwd " do
- to " / et c /passwd "
- li nk_ty pe : hard
- end
56
5.3. Resources and Providers
Cookbook_file
The cookbook_file resource is used to transfer files from a sub-directory of
the files/ directory in a cookbook to a specified path that is located on the host
running the chef-client or chef-solo. The file in a cookbook is selected accord-
ing to file specificity, which allows different source files to be used based on the
hostname, host platform (operating system, distro, or as appropriate), or plat-
form version. Files that are located under COOKBOOK_NAME/files/default
can be used on any platform. Example:
Line 1 cook b o ok_fi l e " /tmp/ t e s t f i l e " do
- s o u r ce " t e s t f i l e "
- mode 00644
- end
Line 1 cook b o ok_fi l e " / e t c /yum . repo s . d/custom . repo " do
- s o u r ce " custom "
- mode 00644
- n o t i f i e s : run , " e xecu t e [ c r e a t e yumcache ] " , : immedi ately
5 n o t i f i e s : c r e ate , " ruby_block [ r e loa d i n t e r n a l yumcache ] " , :
immediately
- end
Template
The template resource is used to manage file contents with an embedded
Ruby (erb) template. This resource includes actions and attributes from the
file resource. Template files managed by the template resource follow the same
file specificity rules as the remote_file and file resources. Example:
Line 1 t em plate " /tmp/ c o n f i g . c o nf " do
- s o u r ce " c o n f i g . c o nf . erb "
- end
Line 1 t em plate " /tmp/ s o m e f i l e " do
- mode 00644
- s o u r ce " s o m e f i l e . erb "
- not_ i f { F i l e . e x i s t s ?( " / e t c /passwd " ) }
5 end
Script
The script resource is used to execute scripts using the specified inter-
preter (Bash, Csh, Perl, Python, or Ruby) and includes all of the actions and
attributes that are available to the execute resource. Example:
Line 1 s c r i p t " i n s t a l l _ s o m e t h i n g " do
- i n t e r p r e t e r " bash "
- u s er " r oot "
- cwd " /tmp "
5 code <<EOH
57
5.3. Resources and Providers
- wget http : / /www. example . com/ t a r b a l l . t a r . gz
- t a r z x f t a r b a l l . t a r . gz
- cd t a r b a l l
- ./ c o n f i g u r e
10 make
- make i n s t a l l
- EOH
- end
User
The user resource is used to add users, update existing users, remove users,
and to lock/unlock user passwords. Example:
Line 1 user " random " do
- supp o rts : manage_home => tru e
- comment "Random User "
- uid 1234
5 gid " u s e r s "
- home " /home/random "
- s h e l l " / bin / bash "
- password " $1$JJsvHslV$szsCjVEroftprNn4JHtDi . "
- end
There are a number of encryption options and tools that can be used to
create a password shadow hash. In general, using a strong encryption method
like SHA-512 and the passwd command in the OpenSSL toolkit is a good
approach, however the encryption options and tools that are available may be
different from one distribution to another. The following examples show how
the command line can be used to create a password shadow hash. When using
the passwd command in the OpenSSL tool:
Line 1 $ o p e n s s l passwd 1 " t h e p l a i n t e x t p a s s w o r d "
When using mkpasswd:
Line 1 $ mkpasswd m sha 512
Another example:
Line 1 user " systemguy " do
- comment " system guy "
- system tru e
- s h e l l " / bin / f a l s e "
5 end
Deploy
The deploy resource is used to manage and control deployments. This is
a popular resource, but is also complex, having the most attributes, multiple
providers, the added complexity of callbacks, plus four attributes that support
layout modifications from within a recipe.
58
5.4. Recipes
The deploy resource is modeled after Capistrano, a utility and framework
for executing commands in parallel on multiple remote machines via SSH. The
deploy resource is designed to behave in a way that is similar to the deploy and
deploy:migration tasks in Capistrano.
5.4 Recipes
Any cookbook contains recipes. The default recipe inside cookbook have
name «default». Let’s add our default recipe, which will install git:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 #
- # Cookbook Name : : my_cool_app
- # Recipe : : d e f a u l t
- #
5 # Copyright (C) 2014 Alexey V a s i l i e v
- #
- # MIT
- #
-
10 package g i t
As you can see, at the beginning of recipe we have comments about this
recipe. Next we add resource «package» with argument «git». The «package»
resource is used to manage packages on the system. For example, on Debian
or Ubuntu resource «package» will use «apt-get» command to install git on
system.
Now you should add «my_cool_app» into run-list to use this cookbook:
Download my-server-cloud/nodes/second.example.com.json
Line 1 {
- " name " : " second . example . com" ,
- " j s o n _ c l a s s " : " Chef : : Node " ,
- " chef_type " : " node " ,
5 " chef_environment " : " development " ,
- " normal " : {
- " fqdn " : " 1 0 . 3 3 . 3 3 . 3 5 "
- } ,
- " d e f a u l t " : {} ,
10 " o v e r r i d e " : { } ,
- " r u n _ l i s t " : [
- " r o l e [ chef c l i e n t ] " ,
- " r o l e [ nginx ] " ,
- " r e c i p e [ my_cool_app ] "
15 ]
- }
59
5.4. Recipes
If you are using Chef Server, don’t forget upload this cookbook and update
node on Chef Server by knife.
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
- $ k n i f e node from f i l e nodes / second . example . com . j s o n
5 Updated Node second . example . com !
- // on r e a l environment you w i l l e xecu t e " k n i f e s sh name : second .
example . com sudo c he f c l i e n t i . . / keys / pro duc t ion . pem x
ubuntu "
- $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- INFO: Chef Run complete i n 2 6.93561 0739 sec ond s
- INFO: Running re p o r t h and l er
Let’s install also ntp package in the same recipe. Because we have in recipe
Ruby syntax, we can little DRY our code:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 %w( g i t ntp ) . each do | pack |
- package pack
- end
Again upload cookbook and run chef-client:
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
- // on r e a l environment you w i l l e xecu t e " k n i f e s sh name : second .
example . com sudo c he f c l i e n t i . . / keys / pro duc t ion . pem x
ubuntu "
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- INFO: Chef Run complete i n 2 6.93561 0739 sec ond s
- INFO: Running re p o r t h and l er
- $ vagrant s sh c hef_ s econ d _ clie n t
- . . .
10 v a gra n t@p r ecis e64 : ~ $ ps ax | grep ntp
- 1115 ? Ss 0 :00 / u s r / sbin / ntpd p / var / run/ntpd . pid g
u 103 : 108
- 13839 pts /2 S+ 0 : 00 grep c o l o r=auto ntp
- v a gra n t@p r ecis e64 : ~ $ g i t v e r s i o n
- g i t v e r s i o n 1 . 7 . 9 . 5
As you can see our simple cookbook is working.
Assign Dependencies
If a cookbook has a dependency on a recipe that is located in another
cookbook, that dependency must be declared in the metadata.rb file for that
cookbook using the depends keyword.
For example, if the following recipe is included in a cookbook named
«my_app»:
60
5.4. Recipes
Line 1 i n c l u d e _ r e c i p e " apache2 : : mod_ssl "
Then the metadata.rb file for that cookbook would have:
Line 1 depends " apache2 "
Create Exceptions
A recipe can write events to a log file and can cause exceptions using Chef
::Log. The levels include debug, info, warn, error, and fatal. For example, to
just capture information:
Line 1 Chef : : Log . i n f o ( some u s e f u l i nfo r mat i on )
Or to trigger a fatal exception:
Line 1 Chef : : Log . f a t a l ! ( something bad )
Include Recipes
A recipe can include one (or more) recipes located in external cookbooks
by using the include_recipe method. When a recipe is included, the resources
found in that recipe will be inserted (in the same exact order) at the point
where the include_recipe keyword is located. The syntax for including a recipe
is like this:
Line 1 i n c l u d e _ r e c i p e " r e c i p e "
For example:
Line 1 i n c l u d e _ r e c i p e " apache2 : : mod_ssl "
If the include_recipe method is used more than once to include a recipe, only
the first inclusion is processed and any subsequent inclusions are ignored.
Reload Attributes
Attributes sometimes depend on actions taken from within recipes, so it
may be necessary to reload a given attribute from within a recipe. For example:
Line 1 ruby_block some_code do
- blo c k do
- node . f r o m _ f i l e ( run_context . r e s o l v e _ a t t r i b u t e ( "COOKBOOK_NAME" ,
"ATTR_FILE" ) )
- end
5 a c t i o n : n ot hing
- end
61
5.5. Attributes
Accessor Methods
Attribute accessor methods are automatically created and the method in-
vocation can be used interchangeably with the keys. For example:
Line 1 d e f a u l t . apache . d i r = " / e t c / apache2 "
- d e f a u l t . apache . l i s t e n _ p o r t s = [ " 80 " , " 443 " ]
This is a matter of style and preference for how attributes are reloaded
from recipes, and may be seen when retrieving the value of an attribute.
5.5 Attributes
An attribute can be defined in a cookbook (or a recipe) and then used to
override the default settings on a node. When a cookbook is loaded during
a chef-client run, these attributes are compared to the attributes that are
already present on the node. When the cookbook attributes take precedence
over the default attributes, the chef-client will apply those new settings and
values during the chef-client run on the node.
An attribute file is located in the attributes/ sub-directory for a cookbook.
When a cookbook is run against a node, the attributes contained in all at-
tribute files are evaluated in the context of the node object. Node methods
(when present) are used to set attribute values on a node. For example, the
«apache2» cookbook contains an attribute file called default .rb, which contains
the following attributes:
Line 1 d e f a u l t [ " apache " ] [ " d i r " ] = " / e t c / apache2 "
- d e f a u l t [ " apache " ] [ " l i s t e n _ p o r t s " ] = [ " 80 " , " 443 " ]
The use of the node object (node) is implicit in the previous example; the
following example defines the node object itself as part of the attribute:
Line 1 node . d e f a u l t [ " apache " ] [ " d i r " ] = " / e t c / apache2 "
- node . d e f a u l t [ " apache " ] [ " l i s t e n _ p o r t s " ] = [ " 80 " , " 443 " ]
In our cookbook «my_cool_app» we want create directory for web app,
add to this directory html file, generate config for nginx and enable this con-
figuration. Let’s add all this by using Chef attributes and resources.
Download my-server-cloud/site- . . .
Line 1 d e f a u l t [ my_cool_app ] [ web_dir ] = / var /www/my_cool_app
- d e f a u l t [ my_cool_app ] [ u s e r ] = vagrant
- d e f a u l t [ my_cool_app ] [ name ] = my_cool_app
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 # i n s t a l l needed package
- %w( g i t ntp ) . each do | pack |
- package pack
62
5.5. Attributes
- end
5
- # c r e a t e d i r e c t o r y f o r web app
- d i r e c t o r y node [ my_cool_app ] [ web_dir ] do
- owner node [ my_cool_app ] [ u s e r ]
- mode " 0755 "
10 r e c u r s i v e t rue
- end
-
- # upload ind ex . html f i l e to web app d i r e c t o r y a s i ndex . html
- c o o kbook _ f ile "#{node [ my_cool_app ] [ web_dir ] } / i nd ex . html " do
15 owner node [ my_cool_app ] [ u s e r ]
- s o u r ce " index . html "
- mode 0755
- end
-
20 # c r e a t e nginx c o n f i g from t emla te nginx . c onf . erb
- n ginx_ con fig = "#{node [ nginx ] [ d i r ] } " +
- " / s i t e s a v a i l a b l e /#{node [ my_cool_app ] [ name ] } . c onf "
- templ at e nginx_co nfi g do
- s o u r ce " nginx . c onf . erb "
25 mode " 0644 "
- end
-
- # a c t i v a t e nginx . c onf i n nginx
- n g i nx_si t e "#{node [ my_cool_app ] [ name ] } . c o nf "
Download my-server-cloud/site- . . .
Line 1 s e r v e r {
- l i s t e n 80 d e f a u l t ;
- c h a r s e t utf 8;
- r o o t <%= node [ my_cool_app ] [ web_dir ] %> ;
5 }
Download my-server-cloud/site- . . .
Line 1 <!DOCTYPE html>
- <html lang=" en ">
- <head>
- <meta c h a r s e t=" utf 8" />
5 <meta httpe qui v="XUACompatible " con ten t=" IE=edge " />
- <t i t l e>My c o o l app</ t i t l e>
- <meta name=" viewp ort " c ont ent=" width=dev ice width , i n i t i a l s c a l e
=1.0 , user s c a l a b l e =0, maximum s c a l e =1.0 " />
- </head>
- <body>
10 <h1>This i s my c o o l web app</h1>
- </body>
- </html>
63
5.5. Attributes
Download my-server-cloud/site-cookbooks/my_cool_app/metadata.rb
Line 1 name my_cool_app
- m aint aine r Alexey V a s i l i e v
- maintainer_email leopard_ne@inbox . ru
- l i c e n s e MIT
5 d e s c r i p t i o n I n s t a l l s / C o n f igures my_cool_app
- l o n g _ d e s c r i p t i o n IO . re ad ( F i l e . j o i n ( F i l e . dirname (__FILE__) , ’README
.md ) )
- v e r s i o n 0 . 1 . 0
-
- r e c i p e my_cool_app , C onfi gur e my c o o l app
10
- depends nginx , ~> 2 . 2 . 0
As you can see, we add default attributes for recipe. Better to use for
cookbook attribute name as root for all attributes - in this case you will not
have problem, if several cookbooks will use similar keys for settings. As you can
see all attributes of this cookbook located inside «my_cool_app» attribute.
In recipe we add needed commands to create and activate our index.html.
Also we add to metadata information about recipe and dependence to nginx
cookbook (because we used inside our recipe «nginx_site» resource). After
upload cookbook at Chef server and run chef-client at the second node, we can
see results at http://10.33.33.35 (Pic 5.1):
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
- // on r e a l environment you w i l l e xecu t e " k n i f e s sh name : second .
example . com sudo c he f c l i e n t i . . / keys / pro duc t ion . pem x
ubuntu "
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- . . .
- INFO: d i r e c t o r y [ / var /www/my_cool_app ] c r e a t e d d i r e c t o r y / var /www/
my_cool_app
- INFO: d i r e c t o r y [ / var /www/my_cool_app ] owner changed to 1000
- INFO: d i r e c t o r y [ / var /www/my_cool_app ] mode changed to 755
10 INFO: c o okbo o k _fil e [ / var /www/my_cool_app/ ind ex . html ] c r e a t e d f i l e
/ var /www/my_cool_app/ i nd ex . html
- INFO: c o okbo o k _fil e [ / var /www/my_cool_app/ ind ex . html ] updated f i l e
c ontents / var /www/my_cool_app/ i nd ex . html
- INFO: c o okbo o k _fil e [ / var /www/my_cool_app/ ind ex . html ] owner changed
to 1000
- INFO: c o okbo o k _fil e [ / var /www/my_cool_app/ ind ex . html ] mode changed
to 755
- INFO: t emplate [ / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf ]
c r e a t e d f i l e / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf
15 INFO: t emplate [ / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf ]
updated f i l e contents / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app .
conf
- INFO: t emplate [ / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf ] mode
changed to 644
64
5.6. Templates
- INFO: e xecu t e [ n x e n s i t e my_cool_app . c onf ] ran s u c c e s s f u l l y
- INFO: e xecu t e [ n x e n s i t e my_cool_app . c onf ] s e ndi ng r e l o a d a c t i o n to
s e r v i c e [ nginx ] ( d ela yed )
- INFO: s e r v i c e [ nginx ] r e l o a d e d
20 . . .
Figure 5.1: Our cool app
5.6 Templates
As you can see in previous chapter we used resource template for generate
nginx config in default recipe. A cookbook template is a file written in a
markup language that allows the contents of a file to be dynamically generated
based on variables or complex logic. Templates can contain Ruby expressions
and statements. Templates are a great way to manage configuration files across
an organization. A template requires a template resource being added to a
recipe and then a corresponding Embedded Ruby (ERB) template being added
to a cookbook.
To use a template, two things must happen:
A template resource must be added to a recipe
An Embedded Ruby (ERB) template must be added to a cookbook
For example, the following template file and template resource settings can
be used to manage a configuration file named /etc/sudoers. Within a cookbook
that uses sudo, the following resource could be added to recipes/default .rb:
Line 1 t em plate " / e t c / sud o e rs " do
- s o u r ce " sudo e r s . erb "
- mode 0440
- owner " r o ot "
5 group " r oot "
- v a r i a b l e s ({
65
5.6. Templates
- : sudoers_groups => node [ : a u t h o r i z a t i o n ] [ : sudo ] [ : groups ] ,
- : sudo ers_ user s => node [ : a u t h o r i z a t i o n ] [ : sudo ] [ : u s e r s ]
- })
10 end
And then create a template called sudoers.erb and save it to templates/default
/sudoers.erb:
Line 1 #
- # / e t c / sud o e rs
- #
- # Generated by Chef f o r <%= node [ : fqdn ] %>
5 #
-
- D e f a u l t s ! l e c t u r e , t t y _ t i c k e t s , ! fqdn
-
- # User p r i v i l e g e s p e c i f i c a t i o n
10 root ALL=(ALL) ALL
-
- <% @sudoers_users . each do | u s e r | %>
- <%= u s e r %> ALL=(ALL) <%= "NOPASSWD: " i f @passwordle ss %>ALL
- <% end %>
15
- # Members o f the sysadmin group may gain r o ot p r i v i l e g e s
- %sysadmin ALL=(ALL) <%= "NOPASSWD: " i f @passwordless %>ALL
-
- <% @sudoers_groups . each do | group | %>
20 # Members o f the group <%= group %>’ may ga in r o ot p r i v i l e g e s
- %<%= group %> ALL=(ALL) <%= "NOPASSWD: " i f @p asswordless %>ALL
- <% end %>
And then set the default attributes in attributes/default .rb:
Line 1 d e f a u l t [ " a u t h o r i z a t i o n " ] [ " sudo " ] [ " groups " ] = [ " sysadmin " , " wheel " ,
" admin " ]
- d e f a u l t [ " a u t h o r i z a t i o n " ] [ " sudo " ] [ " u s e r s " ] = [ " j e r r y " , " greg " ]
When a template is rendered, Ruby expressions and statements are eval-
uated by the chef-client. The variables listed in the resource’s variables pa-
rameter and the node object are evaluated. The chef-client then passes these
variables to the template, where they will be accessible as instance variables
within the template; the node object can be accessed just as if it were part of
a recipe, using the same syntax.
For example, a simple template resource like this:
Line 1 node [ : fqdn ] = " l a t t e "
- templ at e " /tmp/ f o o " do
- s o u r ce " f oo . erb "
- v a r i a b l e s ({
5 : x_men => " a r e keen "
- })
- end
And a simple Embedded Ruby (ERB) template like this:
Line 1 The node <%= node [ : fqdn ] %> t h i n k s the xmen <%= @x_men %>
66
5.6. Templates
Would render something like:
Line 1 The node l a t t e t h i n k s the xmen are keen
Even though this is a very simple example, the full capabilities of Ruby
can be used to tackle even the most complex and demanding template require-
ments.
File Specificity
A cookbook will frequently be designed to work across many platforms
and will often be required to distribute a specific file to a specific platform. A
cookbook can be designed to support distributing files across platforms, but
ensuring that the right file ends up on each system.
The pattern for file specificity is as follows:
1. hostnode[:fqdn]
2. node[:platform]node[:platform_version]
3. node[:platform]version_components: The version string is split on decimals
and searched from greatest specificity to least; for example, if the location
from the last rule was centos-5.7.1, then centos-5.7 and centos-5 would
also be searched.
4. node[:platform]
5. default
The naming of folders within cookbook directories must literally match
the host notation used for template specificity matching. For example, if
a host is named «foo.example.com», then the folder must be named «host-
foo.example.com».
A cookbook may have a /templates directory structure like this:
Line 1 tem p lat e s /
- windows 6.2
- windows 6.1
- windows 6.0
5 windows
- d e f a u l t
and a resource that looks something like the following:
Line 1 t em plate "C: \ path \ to \ f i l e \ t e x t _ f i l e . t xt " do
- s o u r ce " t e x t _ f i l e . txt "
- mode 0755
- owner " r o ot "
5 group " r oot "
- end
This resource would be matched in the same order as the /templates di-
rectory structure. For a node named «host-node-desktop» that is running
Windows 7, the second item would be the matching item and the location:
67
5.6. Templates
Line 1 / t empl ates
- windows 6.2/ t e x t _ f i l e . t x t
- windows 6.1/ t e x t _ f i l e . t x t
- windows 6.0/ t e x t _ f i l e . t x t
5 windows/ t e x t _ f i l e . t x t
- d e f a u l t / t e x t _ f i l e . t xt
Partial Templates
A template can be built in a way that allows it to contain references to one
(or more) smaller template files. (These smaller template files are also referred
to as partials.) A partial can be referenced from a template file in one of the
following ways:
By using the Ruby render method in the template file
By using the template resource and the variables parameter
Use the render method in a template to reference a partial template file
with the following syntax:
Line 1 <%= r e nde r " partial_name . t x t . erb " , : o p tio n => {} %>
where partial_name.txt.erb is the name of the partial template file and :option
is one (or more) of the following options:
:cookbook - by default, a partial template file is assumed to be located
in the cookbook that contains the top-level template. Use this option to
specify the path to a different cookbook
: local - indicates that the name of the partial template file should be
interpreted as a path to a file in the local file system or looked up in
a cookbook using the normal rules for template files. Set to true to
interpret as a path to a file in the local file system and to false to use
the normal rules for template files
:source - by default, a partial template file is identified by its file name.
Use this option to specify a different name or a local path to use (instead
of the name of the partial template file)
: variables - a hash of variable_name => value that will be made available
to the partial template file. When this option is used, any variables that
are defined in the top-level template that are required by the partial
template file must have them defined explicitly using this option
For example:
Line 1 <%= r e nde r " simp le . t x t . erb " , : v a r i a b l e s => { : u s er => @user } , :
l o c a l => tru e %>
68
5.7. LWRPs
5.7 LWRPs
A LWRP (Lightweight Resources and Providers) is a part of a cookbook
that is used to extend the chef-client in a way that allows custom actions to
be defined, and then used in recipes in much the same way as any platform
resource. A LWRP has two principal components:
A lightweight resource that defines a set of actions and attributes
A lightweight provider that tells the chef-client how to handle each action,
what to do if certain conditions are met, and so on
In addition, most lightweight providers are built using platform resources
and some lightweight providers are built using custom Ruby code.
Once created, a LWRP becomes a Ruby class within the organization.
During each chef-client run, the chef-client will read the lightweight resources
from recipes and process them alongside all of the other resources. When
it is time to configure the node, the chef-client will use the corresponding
lightweight provider to determine the steps required to bring the system into
the desired state.
Where the lightweight resource represents a piece of the system, its current
state, and the action that is needed to move it to the desired state, a lightweight
provider defines the steps that are required to bring that piece of the system
from its current state to the desired state. A LWRP behaves similar to platform
resources and providers:
A lightweight resource is a key part of a recipe
A lightweight resource defines the actions that can be taken
During a chef-client run, each lightweight resource is identified, and then
associated with a lightweight provider
A lightweight provider does the work to complete the action requested
by the lightweight resource
Lightweight resources and providers are loaded from files that are saved in
the following cookbook sub-directories:
Directory Description
providers/ The sub-directory in which lightweight providers are located.
resources/ The sub-directory in which lightweight resources are located.
The naming patterns of lightweight resources and providers are determined
by the name of the cookbook and by the name of the files in the «resources/»
and «providers/» sub-directories. For example, if a cookbook named «exam-
ple» was downloaded to the chef-repo, it would be located at «/cookbooks/ex-
ample/». If that cookbook contained two resources and two providers, the
following files would be part of the «resources/» directory:
Files Resource Name Generated Class
default.rb example Chef::Resource::Example
custom.rb example_custom Chef::Resource::ExampleCustom
69
5.7. LWRPs
And the following files would be part of the «providers/» directory:
Files Provider Name Generated Class
default.rb example Chef::Provider::Example
custom.rb custom Chef::Provider::ExampleCustom
Let’s add in our «my_cool_app» LWRP, which will add in /etc/ssh/
ssh_known_hosts host.
Resources
First of all we should create directory «resources» and add to it know_host.rb
file with content:
Download my-server-cloud/site- . . .
Line 1 a c t i o n s : c r e a t e , : d e l e t e
- d e f a u l t _ a c t i o n : c r e a t e
-
- a t t r i b u t e : host , : kind_of => Str i ng , : name_attribute => true , :
r e q u i r e d => tru e
5 a t t r i b u t e : key , : kind_of => S t r i n g
- a t t r i b u t e : port , : kind_of => Fixnum , : d e f a u l t => 22
- a t t r i b u t e : known_hosts_file , : kind_of => Stri ng , : d e f a u l t => / e t c
/ ssh / ssh_known_hosts
-
- # Needed f o r Chef v e r s i o n s < 0 . 1 0 . 1 0
10 d e f i n i t i a l i z e (* a r g s )
- super
- @action = : c r e a t e
- end
Let’s take it line by line. The first line specifies the allowed actions. Actions
are what your resource can do, e.g. start, stop, create, delete, etc. In this case,
you can : create or : delete known host. The next line defines the default_action
for our resource, in this case : create. If you don’t specify an action when you
use the resource in a recipe, it will default to creating a known host, which is
what you probably want. A general philosophy of Chef is to define intelligent
or «sane» defaults.
Lines 4-7 define attributes, or properties of the known host resource
we are creating. Line 4 defines an :host attribute. Its :name_attribute is
true, which means that this attribute will be set to the string between
my_cool_app_know_host and do. Example:
Line 1 my_cool_app_know_host "Add git hub hos t " do
- hos t githu b . com
- end
-
5 my_cool_app_know_host g ithub . com do
- # The : host a t t r i b u t e w i l l be s e t to git hub . com
- end
70
5.7. LWRPs
In the second example above, the :host attribute will be set to «github.com».
Also, on line 4, we are defining the kind_of validation parameter to tell
the resource which kind of data we should expect (in this case, a string),
whether this attribute is required (yes). Line 5 defines a :key attribute, which
is an optional string with no default. Line 6 defines a :port attribute, a Ruby
Fixnum (i.e. an integer) with a default of 22, which is the default when you
create a known host. Line 7 defines a :known_hosts_file attribute, a string with
a default of /etc/ssh/ssh_known_hosts, which is the default file with known hosts
for ssh client.
For example, the cron_d lightweight resource (found in the cron cookbook)
can be used to manage files located in /etc/cron.d:
Line 1 a c t i o n s : c r e a t e , : d e l e t e
- d e f a u l t _ a c t i o n : c r e a t e
-
- a t t r i b u t e : name , : kind_of => S tri n g , : name_attribute => tru e
5 a t t r i b u t e : cookbook , : kind_of => Stri ng , : d e f a u l t => " cron "
- a t t r i b u t e : minute , : kind_of => [ I n t e g e r , S t r i n g ] , : d e f a u l t => " * "
- a t t r i b u t e : hour , : kind_of => [ Integer , S t r i n g ] , : d e f a u l t => " * "
- a t t r i b u t e : day , : kind_of => [ I nteger , S t r i n g ] , : d e f a u l t => " * "
- a t t r i b u t e : month , : kind_of => [ I nteger , S t r i n g ] , : d e f a u l t => " * "
10 a t t r i b u t e : weekday , : kind_of => [ I nteger , S t r i n g ] , : d e f a u l t => " * "
- a t t r i b u t e : command , : kind_of => Stri ng , : r e q u i r e d => tru e
- a t t r i b u t e : user , : kind_of => S t rin g , : d e f a u l t => " r o o t "
- a t t r i b u t e : mailto , : kind_of => [ S t rin g , N i l C l a s s ]
- a t t r i b u t e : path , : kind_of => [ S trin g , N i l C l a s s ]
15 a t t r i b u t e : home , : kind_of => [ S t rin g , N i l C l a s s ]
- a t t r i b u t e : s h e l l , : kind_of => [ S trin g , N i l C l a s s ]
where
the actions allow a recipe to manage entries in a crontab file (create entry,
delete entry)
: create is the default action
:minute, :hour, :day, :month, and :weekday are the collection of attributes
used to schedule a cron job, assigned a default value of «*»
:command is the command that will be run (and also required)
:user is the user by which the command is run
:mailto, :path, :home, and : shell are optional environment variables that
do not have default value, which each being defined as an array that
supports the String and NilClass Ruby classes
Providers
Now we need to create file know_host.rb in «providers» directory:
Download my-server-cloud/site- . . .
Line 1 # Support whyrun
71
5.7. LWRPs
- d e f whyrun_supported ?
- true
- end
5
- u s e _ i n l i n e _ r e s o u r c e s
-
- a c t i o n : c r e a t e do
- key , comment = i n s u r e _ f o r _ f i l e ( new_resource )
10 # Use a Ruby bl o ck t o e d i t the f i l e
- ruby_block " add #{new_resource . hos t } to #{new_resource .
known_hosts_file } " do
- blo c k do
- f i l e = : : Chef : : U t i l : : Fi l e E d i t . new ( new_resource .
known_hosts_file )
- f i l e . insert_line_if_no_match (/#{Regexp . esc a pe ( comment ) }|#{
Regexp . e scap e ( key ) } / , key )
15 f i l e . w r i t e _ f i l e
- end
- end
- new_resource . updated_by_last_action ( t rue )
- end
20
- a c t i o n : d e l e t e do
- key , comment = i n s u r e _ f o r _ f i l e ( new_resource )
- # Use a Ruby bl o ck t o e d i t the f i l e
- ruby_block " d e l #{new_resource . h ost } from #{new_resource .
known_hosts_file } " do
25 blo c k do
- f i l e = : : Chef : : U t i l : : Fi l e E d i t . new ( new_resource .
known_hosts_file )
- f i l e . s e a r c h _ f i l e _ d e l e t e _ l i n e (/#{Regexp . esc a pe ( comment ) }|#{
Regexp . e scap e ( key ) } /)
- f i l e . w r i t e _ f i l e
- end
30 end
- new_resource . updated_by_last_action ( t rue )
- end
-
- d e f i n s u r e _ f o r _ f i l e ( new_resource )
35 key = ( new_resource . key | | sshkeyscan H p #{ new_resource .
por t } #{new_resource . hos t } 2>&1)
- comment = key . s p l i t ( " \n " ) . f i r s t | | " "
-
- Chef : : A p p l i c a t i on . f a t a l ! " Could not r e s o l v e #{new_resource . hos t }
" i f key =~ / g e t a d d r i n f o /
-
40 # Ensure tha t the f i l e e x i s t s and has minimal c ont ent ( r e q u i r e d
by Chef : : U t i l : : Fi l e E d i t )
- f i l e new_resource . known_hosts_file do
- a c t i o n : c r e a t e
- backup f a l s e
- con tent ’# This f i l e must c ontai n a t l e a s t one l i n e .
This i s t hat l i n e .
72
5.7. LWRPs
45 o n l y _ i f do
- ! : : F i l e . e x i s t s ?( new_resource . known_hosts_file ) | | : : F i l e . new
( new_resource . known_hosts_file ) . r e a d l i n e s . l e n g th == 0
- end
- end
- [ key , comment ]
50 end
DSL Methods
action
The action method is used to define the steps that will be taken for each
of the possible actions defined by the lightweight resource. Each action must
be defined in separate action blocks within the same file. The syntax for the
action method is as follows:
Line 1 a c t i o n : action_name do
- i f @curren t_ resource . e x i s t s
- Chef : : Log . i n f o "#{ @new_resource } a l r e a d y e x i s t s noth in g to
do . "
- e l s e
5 r e s o u r c e " resource_name " do
- Chef : : Log . i n f o "#{ @new_resource } c r e a t e d . "
- end
- end
- new_resource . updated_by_last_action ( t rue )
10 end
where:
:action_name corresponds to an action defined by a lightweight resource
if @current_resource.exists is a condition test that is using an instance vari-
able to see if the object already exists on the node; this is an example of
a test for idempotence
If the object already exists, a @new_resource already exists - nothing to
do. log entry is created
If the object does not already exists, the resource block is run. This
block is a recipe that tells the chef-client what to do. A @new_resource
created. log entry is created
current_resource
The current_resource method is used to represent a resource as it exists on
the node at the beginning of the chef-client run. In other words: what the
resource is currently. The chef-client compares the resource as it exists on the
node to the resource that is created during the chef-client run to determine
what steps need to be taken to bring the resource into the desired state. This
method is often used as an instance variable (@current_resource).
For example:
73
5.7. LWRPs
Line 1 a c t i o n : add do
- u n l e s s @cu rr ent_resourc e . e x i s t s
- cmd = "#{appcmd} add app / s i t e . name:\"#{ @new_resource . app_name
}\ " "
- cmd << " / path :\"#{ @new_resource . path }\" "
5 cmd << " / a p p l i c a t i o n P o o l :\"#{ @new_resource . a p p l i c a t i o n _ p o o l
}\ " " i f @new_resource . a p p l i c a t i o n _ p o o l
- cmd << " / p h ysic a lPat h :\"#{ @new_resource . physi cal_pat h }\ " " i f
@new_resource . physica l_ path
- Chef : : Log . debug (cmd)
- s h e l l _ o ut ! ( cmd)
- Chef : : Log . i n f o ( "App c r e a t e d " )
10 e l s e
- Chef : : Log . debug ( "#{@new_resource } app a l r e a d y e x i s t s nothi ng
to do " )
- end
- end
where the unless conditional statement checks to make sure the resource
doesn’t already exist on a node, and then runs a series of commands when
it doesn’t. If the resource already exists, the log entry would be «Foo app
already exists - nothing to do.»
load_current_resource
The load_current_resource method is used to find a resource on a node based
on a collection of attributes. These attributes are defined in a lightweight
resource and are loaded by the chef-client when processing a recipe during a
chef-client run. This method will ask the chef-client to look on the node to see
if a resource exists with specific matching attributes.
For example:
Line 1 def l oad _cu r ren t_r e sou rce
- @ current_r es ource = Chef : : Resource : : T r ansmis s i o n Torren t F i l e . new (
@new_resource . name )
- Chef : : Log . debug ( "#{@new_resource} t o r r e n t hash = #{torrent_hash }
" )
- path = " foo :#{ @new_resource . a t t 1 }@#{@new_resource . a t t 2 }:#{
@new_resource . a t t 3 }/ path "
5 @tran smi ssion = Opscode : : Tr ansmiss io n : : C l i e n t . new ( path )
- @ torrent = n i l
- b egin
- @ torrent = @tran smi ssion . g et_to r r e nt ( t or rent_hash )
- i n f o = " Found e x i s t i n g #{@new_resource} i n swarm "+
10 " with name o f #{ @t orrent . name } and s t a t u s o f #{ @ torrent .
status_message } "
- Chef : : Log . i n f o ( i n f o )
- @ current_r es ource . t o r r e n t ( @new_resource . t o r r e n t )
- r escu e
- Chef : : Log . debug ( " Cannot f i n d #{@new_resource } in the swarm " )
15 end
- @ current_r es ource
74
5.7. LWRPs
- end
In the previous example, if a resource exists with matching attributes,
the chef-client does nothing and if a resource does not exist with matching
attributes, the chef-client will enforce the state declared in new_resource.
new_resource
The new_resource method is used to represent a resource as loaded by the
chef-client during the chef-client run. In other words: what the resource should
be. The chef-client compares the resource as it exists on the node to the
resource that is created during the chef-client run to determine what steps
need to be taken to bring the resource into the desired state.
For example:
Line 1 a c t i o n : d e l e t e do
- i f e x i s t s ?
- i f : : F i l e . w r i t a b l e ? ( new_resource . path )
- Chef : : Log . i n f o ( " D e l e t i n g #{new_resource } at #{new_resource .
path } " )
5 : : F i l e . d e l e t e ( new_resource . path )
- new_resource . updated_by_last_action ( t rue )
- e l s e
- r a i s e " Cannot d e l e t e #{new_resource } at #{new_resource . path
} ! "
- end
10 end
- end
where the chef-client checks to see if the file exists, then if the file is writable,
and then attempts to delete the resource. path is an attribute of the new
resource that is defined by the lightweight resource.
updated_by_last_action
The updated_by_last_action method is used to notify a lightweight resource
that a node was updated successfully. For example, the cron_d lightweight
resource in the cron cookbook:
Line 1 a c t i o n : c r e a t e do
- t = template " / e t c / cron . d/#{new_resource . name} " do
- cookbook new_resource . cookbook
- s o u r ce " cron . d . erb "
5 mode " 0644 "
- v a r i a b l e s ({
- : name => new_resource . name ,
- : minute => new_resource . minute ,
- : hour => new_resource . hour ,
10 : day => new_resource . day ,
- : month => new_resource . month ,
- : weekday => new_resource . weekday ,
- : command => new_resource . command ,
75
5.7. LWRPs
- : u s er => new_resource . user ,
15 : mail t o => new_resource . mailto ,
- : path => new_resource . path ,
- : home => new_resource . home ,
- : s h e l l => new_resource . s h e l l
- })
20 a c t i o n : cr e a t e
- end
- t . run_action ( : c r e a t e )
- new_resource . updated_by_last_action ( t . updated_by_last_action ?)
- end
where t.updated_by_last_action? uses a variable to check whether a
new crontab entry was created. Also you should remember that t .
updated_by_last_action? will work only with run_action, without it this method
will return always false.
use_inline_resources
A lightweight resource should be set to inline compile mode by adding the
use_inline_resources method at the top of the provider. This ensures that noti-
fications work properly across the resource collection. The use_inline_resources
method was added to the chef-client starting in version 11.0 to address the
behavior described below. The use_inline_resources method should be consid-
ered a requirement for any lightweight resource authored against the 11.0+
versions of the chef-client. This behavior will become the default behavior in
an upcoming version of the chef-client.
The reason why the use_inline_resources method exists at all is due to how
the chef-client processes resources. Currently, the default behavior of the chef-
client processes a single collection of resources, converged on the node in order.
A lightweight resource is often implemented using the core chef-client re-
sources file , template, package, and so on—as building blocks. A lightweight
resource is then added to a recipe using the short name of the lightweight
resource in the recipe (and not by using any of the building block resource
components).
This situation can create problems with notifications because the chef-client
includes embedded resources in the «single collection of resources» after the
parent resource has been fully evaluated.
For example:
Line 1 custom_resource " something " do
- a c t i o n : run
- n o t i f i e s : r e s t a r t , " s e r v i c e [ whatever ] " , : immediate ly
- end
5
- s e r v i c e " whatever " do
- a c t i o n : n ot hing
- end
76
5.7. LWRPs
If the custom_resource is built using the file resource, what happens during
the chef-client run is:
Line 1 custom_resource ( not updated )
- f i l e ( updated )
- s e r v i c e ( skipped , due to : nothing )
The custom_resource is converged completely, its state set to not updated
before the file resource is evaluated. The notifies : restart is ignored and the
service is not restarted.
If the author of the custom resource knows in advance what notification is
required, then the file resource can be configured for the notification in the
provider. For example:
Line 1 a c t i o n : run do
- f i l e " /tmp/ foo " do
- owner " r o o t "
- group " r oot "
5 mode " 0644 "
- n o t i f i e s : r e s t a r t , " s e r v i c e [ whatever ] " , : im mediately
- end
- end
And then in the recipe:
Line 1 s e r v i c e " whatever " do
- a c t i o n : n ot hing
- end
This approach works, but only when the author of the lightweight resource
knows what should be notified in advance of the chef-client run. Consequently,
this is less-than-ideal for most situations.
Using the use_inline_resources method will ensure that the chef-client pro-
cesses a lightweight resource as if it were its own resource collection—a «mini
chef-client run», effectively—that is converged before the chef-client finishes
evaluating the parent lightweight resource. This ensures that any notifica-
tions that may exist in the embedded resources are processed as if they were
notifications on the parent lightweight resource. For example:
Line 1 custom_resource " something " do
- a c t i o n : run
- n o t i f i e s : r e s t a r t , " s e r v i c e [ whatever ] " , : immediate ly
- end
5
- s e r v i c e " whatever " do
- a c t i o n : n ot hing
- end
If the custom_resource is built using the file resource, what happens during
the chef-client run is:
Line 1 custom_resource ( s t a r t s conv e rgin g )
- f i l e ( updated )
- custom_resource ( updated , becau se f i l e updated )
77
5.7. LWRPs
- s e r v i c e ( updates , b ec ause : immediately i s s e t i n the custom
r e s o u r c e )
The use_inline_resources method should be considered a default method for
any provider that defines a custom resource. It’s the correct behavior. And it
will soon become the default behavior in a future version of the chef-client.
Because inline compile mode makes it impossible for embedded resources
to notify resources in the parent resource collection, inline compile mode may
cause issues with some provider implementations. In these cases, use a defini-
tion to work around inline compile mode.
whyrun_supported?
why-run mode is a way to see what the chef-client would have configured,
had an actual chef-client run occurred. This approach is similar to the con-
cept of «no-operation» (or «no-op»): decide what should be done, but then
don’t actually do anything until it’s done right. This approach to configu-
ration management can help identify where complexity exists in the system,
where inter-dependencies may be located, and to verify that everything will
be configured in the desired manner.
When why-run mode is enabled, a chef-client run will occur that does ev-
erything up to the point at which configuration would normally occur. This
includes getting the configuration data, authenticating to the server, rebuild-
ing the node object, expanding the run list, getting the necessary cookbook
files, resetting node attributes, identifying the resources, and building the re-
source collection and does not include mapping each resource to a provider or
configuring any part of the system.
When the chef-client is run in why-run mode, certain assumptions are
made:
If the service resource cannot find the appropriate command to verify the
status of a service, why-run mode will assume that the command would
have been installed by a previous resource and that the service would
not be running
For not_if and only_if attribute, why-run mode will assume these are
commands or blocks that are safe to run. These conditions are not
designed to be used to change the state of the system, but rather to
help facilitate idempotency for the resource itself. That said, it may be
possible that these attributes are being used in a way that modifies the
system state
The closer the current state of the system is to the desired state, the
more useful why-run mode will be. For example, if a full run-list is run
against a fresh system, that run-list may not be completely correct on
the first try, but also that run-list will produce more output than smaller
run-list
78
5.7. LWRPs
The whyrun_supported? method is used to set a lightweight provider to sup-
port why-run mode. The syntax for the whyrun_supported? method is as follows:
Line 1 def whyrun_supported ?
- true
- end
where whyrun_supported? is set to true for any lightweight provider that
supports using why-run mode. When why-run mode is supported by the a
lightweight provider, the converge_by method is used to define the strings that
are logged by the chef-client when it is run in why-run mode.
Log entries and rescue
Use the Chef::Log class in a lightweight provider to define log entries that
are created during a chef-client run. The syntax for a log message is as follows:
Line 1 Chef : : Log . log_type ( " message " )
where
log_type can be .debug, .info, .warn, .error, or .fatal
«message» is the message that is logged
For example, from the «repository.rb» provider in the yum cookbook:
Line 1 a c t i o n : add do
- u n l e s s : : F i l e . e x i s t s ?( " / e t c /yum . r e p os . d/#{new_resource . repo_name
} . repo " )
- Chef : : Log . i n f o " Adding #{new_resource . repo_name} r e p o s i t o r y to
/ et c /yum . r e p os . d/#{new_resource . repo_name } . re po "
- rep o_co nfi g
5 end
- end
where the Chef::Log class appends .info as the log type. If the name of
the repo was «fo, then the log message would be «Adding foo repository to
/etc/yum.repos.d/foo.rep.
Another example shows two log entries, one that is triggered when a service
is being restarted, and then another that is triggered after the service has been
restarted:
Line 1 a c t i o n : r e s t a r t do
- i f @curren t_ resource . ru nning
- Chef : : Log . debug " R e s t a r t i n g #{new_resource . service_name } "
- s h e l l _ o ut ! ( restart_command )
5 new_resource . updated_by_last_action ( t rue )
- Chef : : Log . debug " R esta rted #{new_resource . service_name } "
- end
- end
Use the rescue clause to make sure that a log message is always provided.
For example:
79
5.7. LWRPs
Line 1 def l oad _cu r ren t_r e sou rce
- . . .
- b egin
- . . .
5 r escu e
- Chef : : Log . debug ( " Cannot f i n d #{@new_resource } in the swarm " )
- end
- . . .
- end
Using LWRP
We finished with our LWRP. Let’s test it. Just add to «default.rb» you
new LWRP, which will add to known hosts «github.com»:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 # known h o s t s f o r githu b . com
- my_cool_app_know_host g ithub . com
Upload cookbook on server and run chef-client on node:
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
-
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- . . .
- INFO: Found chef c l i e n t i n / usr / b in / c hef c l i e n t
- INFO: r u n i t _ s e r v i c e [ nginx ] c o n f i g u r e d
- INFO: ruby_block [ add gi thub . com to / e t c / s sh /ssh_known_hosts ]
c a l l e d
10 INFO: Chef Run complete i n 1 1.92033 6698 sec ond s
- INFO: Running re p o r t h a ndlers
- INFO: Report h a n d l ers complete
-
- $ vagrant s sh c hef_ s econ d _ clie n t
15
- $ c at / e t c / ssh / ssh_known_hosts
- # This f i l e must c ontai n a t l e a s t one l i n e . This i s tha t l i n e .
- # git hub . com SSH2.0OpenSSH_5 . 9 p1 Debian5ubuntu1+git hub5
- | 1 | fvGKZG+jIkEntM5yBvzJ230TX1o=|9qP2wRdFIS+cAouirLYDb1Ibl7A= ssh
r s a . . .
Let’s check how it will delete this hosts:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 my_cool_app_know_host githu b . com do
- a c t i o n : d e l e t e
- end
Again upload on server and run chef-client:
80
5.8. HWRPs
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
-
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- . . .
- INFO: Found chef c l i e n t i n / usr / b in / c hef c l i e n t
- INFO: r u n i t _ s e r v i c e [ nginx ] c o n f i g u r e d
- INFO: ruby_block [ d e l git hub . com from / e t c / s s h /ssh_known_hosts ]
c a l l e d
10 INFO: Chef Run complete i n 1 7.10937 9291 sec ond s
- INFO: Running re p o r t h a ndlers
- INFO: Report h a n d l ers complete
-
- $ vagrant s sh c hef_ s econ d _ clie n t
15
- $ c at / e t c / ssh / ssh_known_hosts
- # This f i l e must c ontai n a t l e a s t one l i n e . This i s tha t l i n e .
As you can see, all works as expected.
5.8 HWRPs
When Chef first came out, there was no Lightweight Resources and
Providers (LWRP) syntax and any hardcore extension to Chef had to be writ-
ten in Ruby. However, Chef team saw a need to be filled and created LWRP,
making it easier to create your own Resources. The problem comes when
LWRP cannot fulfill all of your needs. This means you need to fall back to
writing pure ruby code. For lack of a better term, I’ll call this method a
HWRP, or Heavyweight Resources and Providers.
While writing a LWRP is meant to be simple and elegant, writing a HWRP
is meant to be flexible. It gives you the full power of ruby in exchange for
elegance.
HWRPs and LWRPS
With LWRP you are taught to create a Resource and a Provider together.
This is the simplest way. However, just because you need to convert a resource
definition or a provider into a HWRP you do not need to convert both.
The LWRP syntax «compiles» into real ruby code, so Chef will not know
the difference in how they were defined. A valid cookbook directory structure:
Line 1 l i b r a r i e s /
- p r o v i d e r _ d e f a u l t . rb
- p r o v i d e r s /
- r e s o u r c e s /
5 d e f a u l t . rb
- r e c i p e s /
- d e f a u l t . rb
- metadata . rb
81
5.8. HWRPs
Anything you put in «resources/» or «providers/» Chef will attempt to
parse at runtime. We don’t want Chef trying to read our HWRP as the
Chef DSL, we want it to interpret it as code. Luckily, anything stored in the
«libraries/» folder Chef will try to import at runtime. A good example of this
can be seen in the runit cookbook.
Example
Let’s go through an example. We are going to create a HWRP that
is very simple, it already written as a LWRP. Our HWRP will called
my_cool_app_known_host, to not conflict with already existed in cookbook
LWRP my_cool_app_know_host.
Resource
First, we need to inherit from the appropriate Chef classes in our HWRP.
Note the class hierarchy as well as the inheritance:
Line 1 r e q u i r e c h e f / r e s o u r c e
-
- c l a s s Chef
- c l a s s Resource
5 c l a s s MyCoolAppKnownHost < Chef : : Resource
-
- # Some Magic Happens
-
- end
10 end
- end
Next, we should to override the initialize method to make sure we have
some defaults. We aren’t defining all of our resource attributes here, just the
ones that need defaults.
Line 1 r e q u i r e c h e f / r e s o u r c e
-
- c l a s s Chef
- c l a s s Resource
5 c l a s s MyCoolAppKnownHost < Chef : : Resource
-
- def i n i t i a l i z e ( name , run_context=n i l )
- super
- # Bind o u r s e l v e s to the name with an u n dersc o re
10 @resource_name = : my_cool_app_known_host
- # We need to t i e to our p r o v i d e r
- @provider = Chef : : Pr ovi d er : : MyCoolAppKnownHost
- # D e f ault Action Goes he re
- @action = : c r e a t e
15 @allowed_a ctions = [ : c r e ate , : d e l e t e ]
-
- # Now we need to s e t up any r e s o u r c e d e f a u l t s
- @port = 22
82
5.8. HWRPs
- @known_hosts_file = / e t c / s sh /ssh_known_hosts
20 @host = name # This i s e q u i v a l e n t to s e t t i n g :
name_attribute => true
- end
-
- end
- end
25 end
Now lets set up some attribute methods in our HWRP. Make sure to read
the code comments for an explanation of what is going on.
Line 1 r e q u i r e c h e f / r e s o u r c e
-
- c l a s s Chef
- c l a s s Resource
5 c l a s s MyCoolAppKnownHost < Chef : : Resource
-
- def i n i t i a l i z e ( name , run_context=n i l )
- super
- # Bind o u r s e l v e s to the name with an u n dersc o re
10 @resource_name = : my_cool_app_known_host
- # We need to t i e to our p r o v i d e r
- @provider = Chef : : Pr ovi d er : : MyCoolAppKnownHost
- # D e f ault Action Goes he re
- @action = : c r e a t e
15 @allowed_a ctions = [ : c r e ate , : d e l e t e ]
-
- # Now we need to s e t up any r e s o u r c e d e f a u l t s
- @port = 22
- @known_hosts_file = / e t c / s sh /ssh_known_hosts
20 @host = name # This i s e q u i v a l e n t to s e t t i n g :
name_attribute => true
- end
-
- # Def i ne the a t t r i b u t e s we s e t d e f a u l t s f o r
- def key ( arg=n i l )
25 set_or_ return ( : key , arg , : kind_of => S t r i n g )
- end
-
- def host ( arg=n i l )
- set_or_ return ( : host , arg , : kind_of => St r i n g )
30 end
-
- def port ( arg=n i l )
- set_or_ return ( : port , arg , : kind_of => I n t e g e r )
- end
35
- def known_hosts_file ( arg=n i l )
- set_or_ return ( : known_hosts_file , arg , : kind_of => S t r i n g )
- end
-
40 end
- end
83
5.8. HWRPs
- end
Providers
Very similar to resources, here is the basic class structure for a provider.
Line 1 r e q u i r e c h e f / p r o v i d e r
-
- c l a s s Chef
- c l a s s Prov ide r
5 c l a s s MyCoolAppKnownHost < Chef : : Pro v ide r
-
- # Magic Happens
-
- end
10 end
- end
While we don’t need to write an initialize method (we can), we do need to
override load_current_resource.
Line 1 r e q u i r e c h e f / p r o v i d e r
-
- c l a s s Chef
- c l a s s Prov ide r
5 c l a s s MyCoolAppKnownHost < Chef : : Pro v ide r
-
- # We MUST o v e r r i d e t h i s method i n our custom p r o v i d e r
- def loa d_c u rre nt_r eso urce
- # Here we keep the e x i s t i n g v e r s i o n o f the r e s o u r c e
10 # i f none e x i s t s we c r e a t e a new one from the r e s o u r c e we
d e f i n e d e a r l i e r
- @c ur rent_resour ce ||= Chef : : Resource : : MyCoolAppKnownHost .
new ( new_resource . name)
-
- # New r e s o u r c e r e p r e s e n t s the c h e f DSL blo ck tha t i s b ein g
run ( from a r e c i p e f o r example )
- @c ur rent_resour ce . key ( new_resource . key )
15 @c ur rent_resour ce . p ort ( new_resource . por t )
- @c ur rent_resour ce . known_hosts_file ( new_resource .
known_hosts_file )
- # Although you can r e f e r e n c e @new_resource throughout the
p r o v i d e r i t i s b e s t to
- # only make m o d i f i c a t i o n s to the c u r r e n t v e r s i o n
- @c ur rent_resour ce . h ost ( new_resource . h ost )
20 @c ur rent_resour ce
- end
-
- end
- end
25 end
84
5.8. HWRPs
Now it is time to define what we do in our actions, with our HWRP we
need to define methods like action_create to define a :create action. Chef will
do some introspection to find these methods and hook them up.
Line 1 r e q u i r e c h e f / p r o v i d e r
-
- c l a s s Chef
- c l a s s Prov ide r
5 c l a s s MyCoolAppKnownHost < Chef : : Pro v ide r
-
- # We MUST o v e r r i d e t h i s method i n our custom p r o v i d e r
- def loa d_c u rre nt_r eso urce
- # Here we keep the e x i s t i n g v e r s i o n o f the r e s o u r c e
10 # i f none e x i s t s we c r e a t e a new one from the r e s o u r c e we
d e f i n e d e a r l i e r
- @c ur rent_resour ce ||= Chef : : Resource : : MyCoolAppKnownHost .
new ( new_resource . name)
-
- # New r e s o u r c e r e p r e s e n t s the c h e f DSL blo ck tha t i s b ein g
run ( from a r e c i p e f o r example )
- @c ur rent_resour ce . key ( new_resource . key )
15 @c ur rent_resour ce . p ort ( new_resource . por t )
- @c ur rent_resour ce . known_hosts_file ( new_resource .
known_hosts_file )
- # Although you can r e f e r e n c e @new_resource throughout the
p r o v i d e r i t i s b e s t to
- # only make m o d i f i c a t i o n s to the c u r r e n t v e r s i o n
- @c ur rent_resour ce . h ost ( new_resource . h ost )
20 @c ur rent_resour ce
- end
-
- def a c t i o n _ c r e a t e
- Chef : : Log . debug ( "#{@new_resource } : Create #{new_resource .
hos t } " )
25 end
-
- def a c t i o n _ d e l e t e
- Chef : : Log . debug ( "#{@new_resource } : D e lete #{new_resource .
hos t } " )
- end
30
- end
- end
- end
Now we can test it.
Line 1 . . .
- my_cool_app_known_host b i tbucket . o rg
To run chef-client in debug mode you should use l attribute with settings
debug. In vagrant you need set log_level for node in Vagrantfile:
Line 1 c h e f _ c l i e n t .vm. p r o v i s i o n : c h e f _ c l i e n t do | c h e f |
-
85
5.8. HWRPs
- c h e f . l o g _ l e v e l = : debug
-
5 . . .
- end
And see our HWRP execution in log:
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
-
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- [ c hef_ s econ d _cli e n t ] Running p r o v i s i o n e r : c h e f _ c l i e n t . . .
- C rea tin g f o l d e r to ho ld c l i e n t key . . .
- Uploading c h e f c l i e n t v a l i d a t i o n key . . .
- . . .
10 DEBUG: my_cool_app_known_host [ b i t b u c k e t . org ] : Create b i t b u c k e t . org
- DEBUG: Saving th e c u r r e n t s t a t e o f node second . example . com
- INFO: Chef Run complete i n 1 5.49157 5653 sec ond s
- . . .
Now let’s add code for actions :create and : delete (this code similar to LWRP
code):
Line 1 r e q u i r e c h e f / p r o v i d e r
-
- c l a s s Chef
- c l a s s Prov ide r
5 c l a s s MyCoolAppKnownHost < Chef : : Pro v ide r
-
- . . .
-
- def a c t i o n _ c r e a t e
10 Chef : : Log . debug ( "#{@new_resource } : Create #{new_resource .
hos t } " )
-
- key , comment = i n s u r e _ f o r _ f i l e ( new_resource )
- # Use a Ruby blo ck to e d i t t he f i l e
- ruby_block " add #{new_resource . hos t } to #{new_resource .
known_hosts_file } " do
15 blo ck do
- f i l e = : : Chef : : U t i l : : Fi l e E d i t . new ( new_resource .
known_hosts_file )
- f i l e . insert_line_if_no_match (/#{Regexp . esc a pe ( comment )
}|#{ Regexp . e sca p e ( key ) } / , key )
- f i l e . w r i t e _ f i l e
- end
20 end
- new_resource . updated_by_last_action ( true )
- end
-
- def a c t i o n _ d e l e t e
25 Chef : : Log . debug ( "#{@new_resource } : D e lete #{new_resource .
hos t } " )
-
86
5.8. HWRPs
- key , comment = i n s u r e _ f o r _ f i l e ( new_resource )
- # Use a Ruby blo ck to e d i t t he f i l e
- ruby_block " d e l #{new_resource . hos t } from #{new_resource .
known_hosts_file } " do
30 blo ck do
- f i l e = : : Chef : : U t i l : : Fi l e E d i t . new ( new_resource .
known_hosts_file )
- f i l e . s e a r c h _ f i l e _ d e l e t e _ l i n e (/#{Regexp . esc a pe ( comment )
}|#{ Regexp . e sca p e ( key ) }/)
- f i l e . w r i t e _ f i l e
- end
35 end
- new_resource . updated_by_last_action ( true )
- end
-
- pr i v a t e
40
- def i n s u r e _ f o r _ f i l e ( new_resource )
- key = ( new_resource . key | | sshkeyscan H p #{
new_resource . por t } #{new_resource . hos t } 2>&1)
- comment = key . s p l i t ( " \n " ) . f i r s t | | " "
-
45 Chef : : Application . f a t a l ! " Could not r e s o l v e #{new_resource
. hos t } " i f key =~ / g e t a d d r i n f o /
-
- # Ensure that the f i l e e x i s t s and has minimal c ont ent (
r e q u i r e d by Chef : : U t i l : : F i l e E d i t )
- f i l e new_resource . known_hosts_file do
- a c t i o n : c r e a t e
50 backup f a l s e
- con tent ’# This f i l e must c o n t a in at l e a s t one
l i n e . This i s t hat l i n e .
- o n l y _ i f do
- ! : : F i l e . e x i s t s ?( new_resource . known_hosts_file ) | | : :
F i l e . new( new_resource . known_hosts_file ) . r e a d l i n e s .
l ength == 0
- end
55 end
- [ key , comment ]
- end
-
- end
60 end
- end
And check how it works:
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
-
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- [ c hef_ s econ d _cli e n t ] Running p r o v i s i o n e r : c h e f _ c l i e n t . . .
- C rea tin g f o l d e r to ho ld c l i e n t key . . .
87
5.9. Definitions
- Uploading c h e f c l i e n t v a l i d a t i o n key . . .
- . . .
10 WARN: Cloning r e s o u r c e a t t r i b u t e s f o r f i l e [ / e t c / ssh /
ssh_known_hosts ] from p r i o r r e s o u r c e (CHEF 3694)
- WARN: P rev i ous f i l e [ / e t c / ssh / ssh_known_hosts ] : / var / c h e f / ca che /
cookbooks /my_cool_app/ p r o v i d e r s /know_host . rb : 3 9 : i n
i n s u r e _ f o r _ f i l e
- WARN: Current f i l e [ / e t c / s sh / ssh_known_hosts ] : / var / c h e f / cache /
cookbooks /my_cool_app/ l i b r a r i e s / provider_known_host . rb : 6 2 : i n
i n s u r e _ f o r _ f i l e
- INFO: ruby_block [ add b i t b u c k e t . org t o / et c / ssh /ssh_known_hosts ]
c a l l e d
- . . .
15
- $ vagrant s sh c hef_ s econ d _ clie n t
-
- v a gra n t@p r ecis e64 : ~ $ c at / e t c / ssh /ssh_known_hosts
- # This f i l e must c ontai n a t l e a s t one l i n e . This i s tha t l i n e .
20 # git hub . com SSH2.0OpenSSH_6 . 2 p2 Ubuntu6ubuntu0 .1+ git hub2
- | 1 | Daaa5BVIzI52zRmJ2ifMfOkkLJE=|Y611mqhJbVkdJ1onVkaqfV+3i k s= . . .
- # bitbucket . org SSH2.0OpenSSH_5 . 3
- | 1 | jycCFcglLajRKYIlyAJaD+zmOjw=|51hFV+x1XIZNdiMGG6K0Xz+Nkds= . . .
As a result, we wrote HWRP, which performs the same job as LWRP.
HWRP is not so simple and elegant as LWRP, but have huge flexibility, because
have more control by Ruby language.
5.9 Definitions
A definition is used to declare resources so they can be added to the re-
source collection. A definition is not a resource or a lightweight resource. A
definition does not have an associated provider. A definition groups two (or
more) resource declarations. There is no limit to the number of resources that
can be part of a definition. All definitions within a cookbook must be located
in the «definitions/» folder. A definition is never declared into a cookbook. A
definition is best-used when:
Data needs to be passed from one (or more) recipes into a single definition
A repeating usage pattern exists for one (or more) resources
An action does not need to be sent directly to a resource (when it does,
it should be sent to a provider)
In our my_cool_app cookbook we are using nginx_site definition from nginx
cookbook.
Right now definitions continue working in Chef, but better to use LWRP
(or HWRP) instead definition (in chapter «?? ??» you will see warning about
this).
88
5.9. Definitions
Example
Let’s create definition in our my_cool_app cookbook. We will move our
nginx conf creation into definition. Our code:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 # c r e a t e nginx c o n f i g from t e mla te nginx . c onf . erb
- n ginx_ con fig = "#{node [ nginx ] [ d i r ] } / s i t e s a v a i l a b l e /#{node [
my_cool_app ] [ name ] } . conf "
- templ at e nginx_co nfi g do
- s o u r ce " nginx . c onf . erb "
5 mode " 0644 "
- end
-
- # a c t i v a t e c o nf i n nginx
- n g i nx_si t e "#{node [ my_cool_app ] [ name ] } . c o nf "
we moved to definition enable_web_site:
Download my-server-cloud/site- . . .
Line 1 d e f i n e : enable_web_site , : e n able => true , : templat e => " s i t e . c onf .
erb " do
- i f params [ : enab l e ]
- # c r e a t e nginx c o n f i g from te m lat e nginx . conf . erb
- nginx _co nfi g = "#{node [ nginx ’ ] [ d i r ] } / s i t e s a v a i l a b l e /#{
params [ : name ] } . c onf "
5 t emplate n ginx_ con fig do
- s o u r ce params [ : template ]
- mode " 0644 "
- end
- # a c t i v a t e c onf i n nginx
10 n ginx _ s i te "#{params [ : name ] } . conf "
- e l s e
- # d e a c t i v a t e c o n f i n nginx
- n ginx _ s i te "#{params [ : name ] } . conf " do
- enab l e f a l s e
15 end
- end
- end
And now we can replace code in 5.9 by call of enable_web_site:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 # ena b le w e bsit e
- enable_web_site node [ my_cool_app ] [ name ] do
- t emplate " nginx . c onf . erb "
- end
Now it remains to check how it works:
89
5.10. Ohai
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
-
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- . . .
- INFO: t emplate [ / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf ]
c r e a t e d f i l e / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf
- INFO: t emplate [ / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf ]
updated f i l e contents / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app .
conf
- INFO: t emplate [ / e t c / nginx / s i t e s a v a i l a b l e /my_cool_app . c o nf ] mode
changed to 644
10 INFO: e xecu t e [ n x e n s i t e my_cool_app . c onf ] ran s u c c e s s f u l l y
- . . .
As you can see our definition works as expected.
5.10 Ohai
Ohai detects data about your operating system. It can be used standalone,
but its primary purpose is to provide node data to Chef.
When invoked, it collects detailed, extensible information about the ma-
chine it’s running on, including Chef configuration, hostname, FQDN, net-
working, memory, CPU, platform, and kernel data.
When Chef configures the node object during each Chef run, these at-
tributes are used by the chef-client to ensure that certain properties remain un-
changed. These properties are also referred to as automatic attributes (which,
as you remember, impossible to override by attributes from cookbooks, envi-
ronments, roles and nodes). For example:
Line 1 node [ platform ] # The pl atfo rm on which a node i s running . This
a t t r i b u t e h e l p s determine which p r o v i d e r s w i l l be used .
- node [ p latfo rm_ ver sion ] # The v e r s i o n o f the plat form . This
a t t r i b u t e h e l p s determine which p r o v i d e r s w i l l be used .
- node [ hostname ] # The host name f o r the node .
Example
Let’s create new recipe, which will use Ohai attributes and create our own
Ohai attributes.
Recipe node
First of all, we will create new recipe «node», which will install Node.js on
nodes in our my_cool_app cookbook.
New default attributes:
90
5.10. Ohai
Download my-server-cloud/site-cookbooks/my_cool_app/attributes/node.rb
Line 1 d e f a u l t [ my_cool_app ] [ node j s ] [ v e r s i o n ] = 0 . 1 0 . 2 6
- d e f a u l t [ my_cool_app ] [ n o dejs ] [ checksum ] = 2340
ec2dce1794f1ca1 c6 85b56840dd515 a2 71b2
- d e f a u l t [ my_cool_app ] [ n o dejs ] [ d i r ] = / u sr / l o c a l
- d e f a u l t [ my_cool_app ] [ n o dejs ] [ s r c _ u r l ] = " http : / / n o dejs . org /
d i s t "
And recipe:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/node.rb
Line 1 i n c l u d e _ r e c i p e " b uil d e s s e n t i a l "
-
- c a s e node [ pla tform _fa mily ]
- when r h e l , f e d o r a
5 package " o p e nssl devel "
- when de bian
- package " l i b s s l dev "
- end
-
10 nod ejs _tar = " nodev#{node [ my_cool_app ] [ nodej s ’ ] [ v e r s i o n ’ ] } .
t a r . gz "
- nodejs_tar_path = nod ejs_t ar
- i f node [ my_cool_app ] [ n o dejs ] [ v e r s i o n ] . s p l i t ( . ) [ 1 ] . to_i >=
5
- nodejs_tar_path = " v#{node [ my_cool_app ] [ node j s ’ ] [ ve r s i o n
]}/#{ nodejs_tar_path } "
- end
15 # Let t he u s e r o v e r r i d e the s o u r c e u r l i n t he a t t r i b u t e s
- n ode js_ src _ur l = "#{node [ my_cool_app ] [ node j s ’ ] [ s r c _ u rl ]}/#{
nodejs_tar_path } "
-
- r e m o t e _ f i l e " / u s r / l o c a l / s r c /#{nod ejs_t ar } " do
- s o u r ce nodejs_sr c_u rl
20 checksum node [ my_cool_app ] [ n odejs ] [ checksum ]
- mode 0644
- a c t i o n : c r e a t e _ i f _missing
- end
-
25 # nosameowner r e q u i r e d overcome " Cannot change ownership " bug
- # on NFSmounted f i l e s y s t e m
- e x ecut e " t a r nosameowner z x f #{nod ejs _tar } " do
- cwd " / usr / l o c a l / s r c "
- c r e a t e s " / u sr / l o c a l / s r c / nodev#{node [ my_cool_app ] [ n o d ejs ’ ] [
v e r s i o n ] } "
30 end
-
- bash " c om pile node . j s " do
- cwd " / usr / l o c a l / s r c /nodev#{node [ my_cool_app ] [ n o dejs ’ ] [
v e r s i o n ] } "
- code <<EOH
91
5.10. Ohai
35 PATH=" / u s r / l o c a l / bin :$PATH"
- . / c o n f i g u r e p r e f i x=#{node [ my_cool_app ] [ n o dejs ’ ] [ d i r ] }
&& \
- make
- EOH
- c r e a t e s " / u sr / l o c a l / s r c / nodev#{node [ my_cool_app ] [ n o d ejs ’ ] [
v e r s i o n ] } / node "
40 end
-
- e x ecut e " n o dejs make i n s t a l l " do
- environment ({ "PATH" => " / usr / l o c a l / bin : / u sr / bin : / bi n :$PATH" } )
- command "make i n s t a l l "
45 cwd " / usr / l o c a l / s r c /nodev#{node [ my_cool_app ] [ n o d ejs ’ ] [
v e r s i o n ] } "
- not_ i f do
- F i l e . e x i s t s ? ( "#{node [ my_cool_app ] [ node j s ’ ] [ d i r ] } / bin / node
" ) &&
- #{ node [ my_cool_app ] [ nodej s ’ ] [ d i r ] } / bin / node v e r s ion .
chomp == " v#{node [ my_cool_app ] [ n o d ejs ’ ] [ v e r s i o n ’ ] } "
- end
50 end
As you can see, we use node[platform_family’] Ohai variable, which help for
us understand type of OS on node and install needed package.
Also we used cookbook buildessential, because we will install node.js from
source. In this case we should add it as dependency in our metadata.rb file:
Line 1 depends nginx , ~> 2 . 2 . 0
- depends bui ld e s s e n t i a l
To use this recipe we should add it in run_list:
Line 1 {
- " r u n _ l i s t " : [
- " r e c i p e [ my_cool_app ] " ,
- " r e c i p e [ my_cool_app : : node ] "
5 ]
- }
But I want run this recipe with default recipe, so I will leave run_list as is
and add node recipe in default recipe:
Line 1 . . .
- i n c l u d e _ r e c i p e my_cool_app : : node
Now it remains to check how it works:
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
-
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- INFO: r e m o t e _ f i l e [ / u sr / l o c a l / s r c /nodev0 . 1 0 . 2 6 . t a r . gz ] c r e a t e d
f i l e / u sr / l o c a l / s r c /nodev0 . 1 0 . 2 6 . t a r . gz
- INFO: r e m o t e _ f i l e [ / u sr / l o c a l / s r c /nodev0 . 1 0 . 2 6 . t a r . gz ] updated
f i l e co n t e n t s / u sr / l o c a l / s r c /nodev0 . 1 0 . 2 6 . t a r . gz
92
5.10. Ohai
- INFO: r e m o t e _ f i l e [ / u sr / l o c a l / s r c /nodev0 . 1 0 . 2 6 . t a r . gz ] mode
changed to 644
- INFO: e xecu t e [ t a r nosameowner z x f nodev0 . 1 0 . 2 6 . t a r . gz ] ran
s u c c e s s f u l l y
10 INFO: bash [ co mpile node . j s ] ran s u c c e s s f u l l y
- INFO: e xecu t e [ n o dejs make i n s t a l l ] ran s u c c e s s f u l l y
-
- $ vagrant s sh c hef_ s econ d _ clie n t
-
15 v a gra n t@p r ecis e64 : ~ $ node v
- v0 . 1 0 . 2 6
As you can see node recipe installed Node.js on our node.
Ohai plugin
In our node recipe we used maybe not best way to check node version, which
already installed on node (if version mismatch - we should install needed). Let’s
create Ohai plugin, which will give for use node.js version from server. First
of all create in my_cool_app new recipe ohai_plugin.rb with content:
Download my-server-cloud/site- . . .
Line 1 t em plate "#{node [ o hai ’ ] [ plugin_path ] } / system_node_js . rb " do
- s o u r ce " p l u g i n s / system_node_js . rb . erb "
- owner " r o ot "
- group " r oot "
5 mode 00755
- v a r i a b l e s (
- : node_js_bin => "#{node [ my_cool_app ] [ no d e js ’ ] [ d i r ] } / bin /
node "
- )
- end
10
- i n c l u d e _ r e c i p e " o hai "
This recipe will generate ohai plugin from system_node_js.rb.erb template.
Next we should create this template in folder «templates/default/plugins»:
Download my-server-cloud/site- . . .
Line 1 p r o v i d e s " system_node_js "
- p r o v i d e s " system_node_js / v e r s i o n "
-
- system_node_js Mash . new u n l e s s system_node_js
5 system_node_js [ : v e r s i o n ] = n i l u n l e s s system_node_js [ : v e r s i o n ]
-
- sta t u s , stdout , s t d e r r = run_command ( : no_status_check => true , :
command => "<%= @node_js_bin %> v e r s i o n " )
-
- system_node_js [ : v e r s i o n ] = s t dou t [ 1 . . 1 ] i f 0 == s t a t u s
93
5.10. Ohai
In first two lines we set by method provides automatic attributes, which will
provide for us this plugin. Most of the information we want to lookup would
be nested in some way, and ohai tends to do this by storing the data in a Mash
(similar to Ruby hash type). This can be done by creating a new mash and
setting the attribute to it. We did this with system_node_js. In the end of code,
plugin set the version of node.js, if node.js installed on node. That’s it!
Also we should add new dependency for our cookbook - ohai cookbook:
Line 1 depends nginx , ~> 2 . 2 . 0
- depends bui ld e s s e n t i a l
- depends o hai
Next, let’s try this plugin by adding node.rb recipe this content:
Line 1 i n c l u d e _ r e c i p e " b uil d e s s e n t i a l "
-
- i n c l u d e _ r e c i p e " my_cool_app : : ohai_p lugin "
- Chef : : Log . i n f o " I n s t a l l e d Node v e r s i o n : #{node [ system_node_js ] [
v e r s i o n ] } " i f node [ system_node_js ]
5
- c a s e node [ pla tform _fa mily ]
- when r h e l , f e d o r a
- package " o p e nssl devel "
- when de bian
10 package " l i b s s l dev "
- end
In this case we can little change our node.js recipe:
Line 1 exe c u te " n o d ejs make i n s t a l l " do
- environment ({ "PATH" => " / usr / l o c a l / bin : / u sr / bin : / bi n :$PATH" } )
- command "make i n s t a l l "
- cwd " / usr / l o c a l / s r c /nodev#{node [ my_cool_app ] [ n o dejs ’ ] [
v e r s i o n ] } "
5 not_ i f { node [ system_node_js ] && node [ system_node_js ] [
v e r s i o n ] == node [ my_cool_app ] [ n o dejs ] [ v e r s i o n ] }
- end
Now we can check how it works:
Line 1 $ k n i f e cookbook upload my_cool_app
- Uploading my_cool_app [ 0 . 1 . 0 ]
- Uploaded 1 cookbook .
-
5 $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- . . .
- INFO: t emplate [ / e t c / che f / oha i_pl ugi n s / system_node_js . rb ] c r e a t e d
f i l e / et c / c h e f / oh a i_p lugi ns / system_node_js . rb
- INFO: t emplate [ / e t c / che f / oha i_pl ugi n s / system_node_js . rb ] updated
f i l e co n t e n t s / e t c / c h e f / oha i_pl ugi n s / system_node_js . rb
- INFO: t emplate [ / e t c / che f / oha i_pl ugi n s / system_node_js . rb ] owner
changed to 0
10 INFO: t emplate [ / e t c / che f / oha i_pl ugi n s / system_node_js . rb ] group
changed to 0
- INFO: t emplate [ / e t c / che f / oha i_pl ugi n s / system_node_js . rb ] mode
changed to 755
94
5.10. Ohai
- . . .
- $ vagrant p r o v i s i o n c hef_ s e cond _ clie n t
- . . .
15 WARN: Current s e r v i c e [ nginx ] : / var / c h e f / cache / cookbooks / nginx /
r e c i p e s / sou r ce . rb : 1 2 3 : in f r om_f i l e
- INFO: I n s t a l l e d Node v e r s i o n : 0 . 1 0 . 2 6
- WARN: Cloning r e s o u r c e a t t r i b u t e s f o r package [ l i b s s l dev ] from
p r i o r r e s o u r c e (CHEF 3694)
- . . .
As you can see, after first provision (knife cook, knife ssh sudo chefclient)
execution Ohai plugin will be installed, but not executed. Only on second
execution chef-client will use Ohai plugin (because Ohai plugins load before
running run_list).
Ohai 7
Ohai 6 had served us well. However, it had an important architectural
limitation that prevented us from implementing some cool ideas, such as dif-
ferentiating collected data as critical or optional. This limitation was because
Ohai 6 treated plugins as monolithic blocks of code.
Ohai 7 introduces a new DSL, which makes it easier to write custom plugins,
provides better code organization, and sets us up for the future. Here is what
an Ohai 7 plugin looks like:
Line 1 Ohai . p l u g i n ( : Name) do
- p r o v i d e s " a t t r i b u t e " , " a t t r i b u t e / s u b a t t r i b u t e "
- depends " k e r n e l " , " u s e r s "
-
5 def a_shared_method
- # some Ruby code tha t d e f i n e s the shar ed method
- a t t r i b u t e Mash . new
- end
-
10 c o l l e c t _ d a t a ( : d e f a u l t ) do
- # some Ruby code
- a t t r i b u t e Mash . new
- end
-
15 c o l l e c t _ d a t a ( : windows ) do
- # some Ruby code tha t g e t s run only on Windows
- a t t r i b u t e Mash . new
- end
-
20 end
Two important pieces of the new DSL are:
collect_data() blocks, which enable better organization of platform-specific
code
depends / provides statements, which enable easier dependency manage-
ment among plugins
95
5.11. Summary
Read more about the new DSL here.
To migrate our plugin on Ohai 7 is very simple:
Download my-server-cloud/site- . . .
Line 1 Ohai . p l u g i n ( : SystemNodeJs ) do
- p r o v i d e s system_node_js
- p r o v i d e s system_node_js / v e r s i o n
-
5 c o l l e c t _ d a t a do
- system_node_js Mash . new
- system_node_js [ : v e r s i o n ] = n i l u n l e s s system_node_js [ : v e r s i o n ]
- s t atus , stdout , s t d e r r = run_command ( : no_status_check => true ,
: command => "<%= @node_js_bin %> v e r s i o n " )
- system_node_js [ : v e r s i o n ] = s tdo u t [ 1 . . 1 ] i f 0 == s t a t u s
10 end
- end
Ohai 7 is backward compatible with existing Ohai 6 plugins. But none of
the new (or future) functionality will be available to version 6 plugins. All of
your existing plugins will continue to work with Ohai 7.
5.11 Summary
A cookbook is the fundamental unit of configuration and policy distribu-
tion. Knowledge of how to write cookbooks is very important to fully use all
power of Chef.
96
6
Testing Cookbooks
Knowing how to write good cookbooks insufficient if its will not be covered
by tests. Like in any good software products, cookbook tests avoid bugs,
mistakes in code, which is very important. In this chapter we look at the tools
that help us test Chef cookbooks.
6.1 Test Types
Exists several types of software testing. Let’s look at some of them.
Unit Testing
Unit testing is a software testing method by which individual units of source
code, sets of one or more computer program modules together with associated
control data, usage procedures, and operating procedures are tested to de-
termine if they are fit for use. Ideally, each test case is independent from
the others. Substitutes such as method stubs, mock objects, fakes, and test
harnesses can be used to assist testing a module in isolation. Unit tests are
typically written and run by software developers to ensure that code meets its
design and behaves as intended.
Integration Testing
Integration testing is the phase in software testing in which individual soft-
ware modules are combined and tested as a group. It occurs after unit testing
and before validation testing. Integration testing takes as its input modules
that have been unit tested, groups them in larger aggregates, applies tests de-
fined in an integration test plan to those aggregates, and delivers as its output
the integrated system ready for system testing.
97
6.2. ChefSpec
Acceptance Testing
Acceptance testing, a testing technique performed to determine whether
or not the software system has met the requirement specifications. The main
purpose of this test is to evaluate the system’s compliance with the business
requirements and verify if it is has met the required criteria for delivery to end
users.
6.2 ChefSpec
ChefSpec is a unit testing framework for testing Chef cookbooks. ChefSpec
makes it easy to write examples and get fast feedback on cookbook changes
without the need for virtual machines or cloud servers. ChefSpec using RSpec
for writing tests, what is why you should know Rspec at least basic stuff.
Installing
Let’s cover our cookbook by chefspec tests. First we should add this gem
in Gemfile:
Line 1 s ource h ttps : / / rubygems . org
-
- gem b e r k s h e l f
- gem f o o d c r i t i c
5 gem thor f o o d c r i t i c
- gem c h e f s p e c
And you should to execute bundle command to install this gem.
Testing
Next we should create config for chefspec:
Download my-server-cloud/site- . . .
Line 1 r e q u i r e c h e f s p e c
- r e q u i r e c h e f s p e c / b e r k s h e l f # i f you are u sing l i b r a r i a n , when
you sho ul d r e q u i r e c h e f s p e c / l i b r a r i a n
-
- RSpec . c o n f i g u r e do | c o n f i g |
5 # S p e c i f y the Chef l o g _ l e v e l ( d e f a u l t : : warn )
- c o n f i g . l o g _ l e v e l = : warn
-
- # S p e c i f y the o p e r a t i n g p latf orm to mock Ohai data from ( d e f a u l t
: n i l )
- c o n f i g . p latf orm = ubuntu
10
- # S p e c i f y the o p e r a t i n g v e r s i o n t o mock Ohai data from ( d e f a u l t :
n i l )
98
6.2. ChefSpec
- c o n f i g . v e r s i o n = 1 2 . 04
- end
And add some unit tests for default recipe:
Download my-server-cloud/site- . . .
Line 1 r e q u i r e sp ec_ hel per
-
- d e s c r i b e my_cool_app : : d e f a u l t do
- l e t ( : chef_run ) { ChefSpec : : Runner . new . con verge ( d e s c r i b e d _ r e c i p e )
}
5
- %w( g i t ntp ) . each do | pack |
- i t " i n s t a l l #{pack } package " do
- exp ect ( chef_run ) . to i n s t a l l _ p a c k a g e ( pack )
- end
10 end
-
- i t c r e a t e d i r e c t o r y f o r web app do
- exp ect ( chef_run ) . to c r e a t e _ d i r e c t o r y ( / var /www/my_cool_app ) .
with (
- u s er : vagran t ,
15 mode : " 0755 "
- )
- end
-
- i t c r e a t e web app nginx c o n f i g do
20 exp ect ( chef_run ) . to c rea te_ tem pla te ( / e t c / nginx / s i t e s
a v a i l a b l e /my_cool_app . conf )
- end
-
- i t e nab l e nginx s e r v i c e do
- exp ect ( chef_run ) . to e n a b l e _ s e r v i c e ( nginx )
25 end
-
- i t i n c l u d e node r e c i p e do
- exp ect ( chef_run ) . to i n c l u d e _ r e c i p e ( my_cool_app : : node )
- end
30
- end
Now we can check our tests:
Line 1 $ rsp e c
-
- FFFFFF
-
5 F i n ished i n 2 . 2 5 sec ond s
- 6 examples , 0 f a i l u r e s
Our tests failed, because we used nginx_site definition, but dont have nginx
service inside our recipe. We can fix this by adding nginx default recipe inside
our default recipe (nginx default recipe contain service resource).
99
6.2. ChefSpec
Line 1 . . .
-
- s e r v i c e nginx do
- supp o rts : s t a t u s => true , : r e s t a r t => true , : r e l o a d => true
5 a c t i o n : s t a r t
- end
You can ask me: «Wait, we already have role nginx inside our node, which
install nginx. Does in this case we will call nginx recipe twice?». Yes, your are
right. But as you remember, main idea of Chef is idempotence, so on second
call of nginx recipe it will do nothing (if recipe written in this way). So we
shouldn’t worry about this:
Download my-server-cloud/site-cookbooks/my_cool_app/recipes/default.rb
Line 1 . . .
-
- # nginx r e c i p e
- i n c l u d e _ r e c i p e nginx
5
- # e n abl e w ebsit e
- enable_web_site node [ my_cool_app ] [ name ] do
- t emplate " nginx . c onf . erb "
- end
10
- . . .
Add test for include_recipe call and check tests again:
Line 1 $ rsp e c
-
- . . . . . . .
-
5 F i n ished i n 2 . 2 5 sec ond s
- 7 examples , 0 f a i l u r e s
As you can see all tests pass.
Now let’s cover our node recipe:
Download my-server-cloud/site- . . .
Line 1 r e q u i r e sp ec_ hel per
-
- d e s c r i b e my_cool_app : : node do
- l e t ( : pla t for m ) { ubuntu }
5 l e t ( : plat f orm_ v ers i on ) { 1 2.04 }
- l e t ( : chef_run ) { ChefSpec : : Runner . new ( p l atf o rm : platform ,
v e r s i o n : p l atfo r m_v e rsio n ) . con verge ( d e s c r i b e d _ r e c i p e ) }
- l e t ( : n odej s _ vers i o n ) { 0 . 1 0 . 2 6 }
- l e t ( : n ode js _ta r ) { " nodev#{n o d ejs_ v e rsio n } . t a r . gz " }
-
10 i t " i n s t a l l l i b s s l dev package " do
- exp ect ( chef_run ) . to i n s t a l l _ p a c k a g e ( l i b s s l dev )
100
6.3. Fauxhai
- end
-
- cont e xt r h e l or f e d o r a do
15 l e t ( : p latf orm ) { red hat }
- l e t ( : p l atfo r m_v e rsio n ) { 6 . 5 }
-
- i t " i n s t a l l o p enss l d e v e l package " do
- exp ect ( chef_run ) . to i n s t a l l _ p a c k a g e ( o pens s l d e v e l )
20 end
- end
-
- i t download node i f m i s sing do
- exp ect ( chef_run ) . to cre a t e _remot e _ f ile_i f _ m issin g ( " / u sr / l o c a l /
s r c /#{nod ejs_t ar } " )
25 end
-
- i t unpack node do
- exp ect ( chef_run ) . to run_execute ( " t a r nosameowner z x f #{
nodej s_tar } " )
- end
30
- i t i n s t a l l node do
- exp ect ( chef_run ) . to run_execute ( make i n s t a l l ) . with (cwd : " /
usr / l o c a l / s r c /nodev#{n o dejs _ v ersi o n } " )
- end
- end
As you can see, you can pass platform and version, which will be used as
Ohai attribute values. This used to check, what depend on platform recipe will
install different dependencies. Our results:
Line 1 $ rsp e c
-
- . . . . . . . . . .
-
5 F i n ished i n 3 . 1 9 sec ond s
- 12 examples , 0 f a i l u r e s
As a result, we covered cookbook by unit tests using chefspec.
6.3 Fauxhai
Ohai is a tool that is used to detect attributes on a node, and then provide
these attributes to the chef-client at the start of every chef-client run. Ohai is
required by the chef-client and must be present on a node. It’s awesome, but
this can be a problem for testing. That is why exists Fauxhai. Fauxhai is a
gem for mocking out ohai data in your chef testing.
101
6.3. Fauxhai
Testing
As you can see from our node specs, we can set platform and version, but we
can stub additional Ohai attribute by using Fauxhai. Let’s look at example:
Line 1 r e q u i r e c h e f s p e c
-
- d e s c r i b e my_cool_app : : d e f a u l t do
- [ 1 , 2 , 4 ] . each do | k e r n e l s |
5 cont e xt " on Ubuntu with #{k e r n e l s } k e r n e l s " do
- l e t ( : chef_run ) { ChefSpec : : ChefRunner . new . con ver ge (
d e s c r i b e d _ r e c i p e ) }
-
- b e f o r e do
- Fauxhai . mock ( pla t for m : ubuntu , v e r s i o n : 12.04 , fqdn :
example . com , cpu : { r e a l => kernel s , t o t a l =>
k e r n e l s })
10 end
-
- i t i n s t a l l htop do
- exp ect ( chef_run ) . to i n s t a l l _ p a c k a g e ( htop )
- end
15
- i t c r e a t e f i l e /tmp/cpu_count do
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( /tmp/ cpu_count ) .
with_content ( k e r n e l s . to_s )
- end
- end
20 end
- end
As you can see from example, we mock platform, fqdn and cpu numbers
from Ohai attributes. And in our recipe we create /tmp/cpu_count file, which
contains number of cpu on node. By tests we check what this works on different
values of Ohai attributes.
Also we can set node attributes by ChefSpec::Runner:
Download my-server-cloud/site- . . .
Line 1 . . .
-
- cont e xt node v e r s i o n s do
- l e t ( : system_node_version ) { n i l }
5 l e t ( : chef_run ) do
- ChefSpec : : Runner . new ( p l atf o rm : platform , v e r s i o n :
plat form _ vers i on ) do | node |
- node . automatic [ system_node_js ] = { v e r s i o n =>
system_node_version } i f system_node_version
- end . con verge ( d e s c r i b e d _ r e c i p e )
- end
10
- i t i n s t a l l node i f v e r s i o n not s p e c i f i e d do
102
6.4. Test Kitchen
- exp ect ( chef_run ) . to run_execute ( make i n s t a l l ) . with (cwd : " /
usr / l o c a l / s r c /nodev#{n o dejs _ v ersi o n } " )
- end
-
15 cont e xt i n s t a l l e d d i f f e r e n t v e r s i o n do
- l e t ( : system_node_version ) { 0 . 8 . 0 }
-
- i t i n s t a l l node i f v e r s i o n i s not t he same do
- exp ect ( chef_run ) . to run_execute ( make i n s t a l l ) . with (cwd :
" / u sr / l o c a l / s r c / nodev#{n odej s _ vers i o n } " )
20 end
- end
-
- cont e xt i n s t a l l e d same v e r s i o n do
- l e t ( : system_node_version ) { n o d ejs_ v e rsio n }
25
- i t do not i n s t a l l node do
- exp ect ( chef_run ) . not_to run_execute ( make i n s t a l l ) . with (
cwd : " / u s r / l o c a l / s r c /nodev#{n odejs _ v ersi o n } " )
- end
- end
30 end
-
- . . .
And check what this new tests is pass:
Line 1 $ rsp e c
-
- . . . . . . . . . . . . . . .
-
5 F i n ished i n 3 . 7 6 sec ond s
- 15 examples , 0 f a i l u r e s
We covered different installation (or not installation) of node.js on node.
6.4 Test Kitchen
Test Kitchen is a test harness tool to execute your configured code on one or
more platforms in isolation. A driver plugin architecture is used which lets you
run your code on various cloud providers and virtualization technologies such as
Amazon EC2, Blue Box, CloudStack, Digital Ocean, Rackspace, OpenStack,
Vagrant, Docker, LXC containers, and more. Many testing frameworks are
already supported out of the box including Bats, shUnit2, RSpec, Serverspec,
etc.
Installing
Let’s cover our cookbook by test kitchen. First we should add this gem in
Gemfile:
103
6.4. Test Kitchen
Line 1 s ource h ttps : / / rubygems . org
-
- gem b e r k s h e l f
- gem f o o d c r i t i c
5 gem thor f o o d c r i t i c
- gem c h e f s p e c
- gem t e s t k i t c h e n
- gem k itc hen vagrant
And you should execute bundle command to install this gems. We can check
what kitchen installed:
Line 1 $ k i t c h e n v e r s i o n
- Test Kitchen v e r s i o n 1 . 2 . 1
- $ k i t c h e n h elp
- Commands :
5 k i t c h e n c o n s o l e # Kitchen Console !
- k i t c h e n c onver ge [INSTANCE |REGEXP| a l l ] # Converge one or more
i n s t a n c e s
- k i t c h e n c r e a t e [INSTANCE|REGEXP| a l l ] # Create one or more
i n s t a n c e s
- k i t c h e n d e s troy [INSTANCE|REGEXP| a l l ] # Destroy one or more
i n s t a n c e s
- k i t c h e n diag n ose [ INSTANCE|REGEXP| a l l ] # Show computed
d i a g n o s t i c c o n f i g u r a t i o n
10 k i t c h e n d r i v e r # Dri ver subcommands
- k i t c h e n d r i v e r c r e a t e [NAME] # Create a new Kitchen
Dri ver gem p r o j e c t
- k i t c h e n d r i v e r d i s c o v e r # Disc over Test Kitchen
d r i v e r s p u b l ished on RubyGems
- k i t c h e n d r i v e r hel p [COMMAND] # D escr i b e subcommands
or one s p e c i f i c subcommand
- k i t c h e n h el p [COMMAND] # D escr i b e a v a i l a b l e
commands or one s p e c i f i c command
15 k i t c h e n i n i t # Adds some
c o n f i g u r a t i o n to your cookbook so Kitchen can r oc k
- k i t c h e n l i s t [INSTANCE |REGEXP| a l l ] # L i s t s one or more
i n s t a n c e s
- k i t c h e n l o g i n INSTANCE|REGEXP # Log i n to one i n s t a n c e
- k i t c h e n s etup [ INSTANCE|REGEXP| a l l ] # Setup one or more
i n s t a n c e s
- k i t c h e n t e s t [INSTANCE|REGEXP| a l l ] # Test one or more
i n s t a n c e s
20 k i t c h e n v e r i f y [INSTANCE|REGEXP| a l l ] # V e r i f y one or more
i n s t a n c e s
- k i t c h e n v e r s i o n # Prin t Kitchen s
v e r s i o n i n f o r m a tion
Now we’ll add Test Kitchen to our project by using the init subcommand:
Line 1 $ k i t c h e n i n i t
- c r e a t e . k i t c h e n . yml
- append T h o r f i l e
- c r e a t e t e s t / i n t e g r a t i o n / d e f a u l t
104
6.4. Test Kitchen
What’s going on here? The kitchen init subcommand will create an initial
configuration file for Test Kitchen called .kitchen.yml. A few directories were
created but these are only a convenience you don’t strictly need «test/inte-
gration/default» in your project. You can see that you have a . gitignore file
in your project’s root which will tell Git to never commit a directory called
.kitchen and something called .kitchen. local .yml. Finally, a gem called kitchen
vagrant was installed. By itself Test Kitchen can’t do very much. It needs one
or more Drivers which are responsible for managing the virtual machines we
need for testing. At present there are many different Test Kitchen Drivers but
we’re going to stick with the Kitchen Vagrant Driver for now.
Let’s turn our attention to the .kitchen.yml file. While Test Kitchen may
have created the initial file automatically, it’s expected that you will read and
edit this file. Opening this file in your editor of choice we see something like
the following:
Line 1
- d r i v e r :
- name : vagrant
-
5 p r o v i s i o n e r :
- name : c h ef_sol o
-
- platform s :
- name : ubuntu 12.04
10 name : cen to s 6.4
-
- s u i t e s :
- name : d e f a u l t
- r u n _ l i s t :
15 r e c i p e [ my_cool_app : : d e f a u l t ]
- a t t r i b u t e s :
Very briefly we can cover the 4 main sections you’re likely to find in a
.kitchen.yml file:
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 responsible for requiring and using
the configuration here. Under this section we have driver .name: This tells
Test Kitchen that we want to use the kitchenvagrant driver by default
unless otherwise specified.
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 ap-
proach is to use chefsolo, but other options are available, and ultimately
Test Kitchen doesn’t care how the infrastructure is built - it could theo-
retically 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
105
6.4. Test Kitchen
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 separately from the server cookbook code for maximum isolation.
Let’s say for argument’s sake that we only care about running our Chef
cookbook on Ubuntu 12.04 distributions. In that case, we can edit the .kitchen
.yml file so that the list of platforms has only one entry like so:
Line 1
- d r i v e r :
- name : vagrant
-
5 p r o v i s i o n e r :
- name : c h ef_sol o
-
- platform s :
- name : ubuntu 12.04
10
- s u i t e s :
- name : d e f a u l t
- r u n _ l i s t :
- r e c i p e [ my_cool_app : : d e f a u l t ]
15 a t t r i b u t e s :
To see the results of our work, let’s run the kitchen list subcommand:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo <Not Created>
So what’s this «default-ubuntu-1204» thing and what’s an «Instance»? A
Test Kitchen Instance is a pairwise combination of a Suite and a Platform
as laid out in your .kitchen.yml file. Test Kitchen has auto-named your only
instance by combining the Suite name («default») and the Platform name
(«ubuntu-12.04») into a form that is safe for DNS and hostname records,
namely «default-ubuntu-1204».
Okay, let’s spin this Instance up to see what happens. Test Kitchen calls
this the Create Action. We’re going to be painfully explicit and ask Test
Kitchen to only create the «default-ubuntu-1204» instance:
Line 1 $ k i t c h e n c r e a t e d e f a u l t ubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > Cre ati ng <d e f a u l t ubuntu 1204 >...
- Bring ing machine d e f a u l t up with v irtualbo x p r o v i d e r . . .
5 [ d e f a u l t ] Impo rting base box opscodeubuntu 1 2 . 0 4 . . .
- [ d e f a u l t ] Matching MAC ad d r ess f o r NAT networking . . .
- [ d e f a u l t ] S e t t i n g th e name o f the VM. . .
- [ d e f a u l t ] C l earing any p r e v i o u s l y s e t f orwarded p o r t s . . .
- [ d e f a u l t ] C l earing any p r e v i o u s l y s e t network i n t e r f a c e s . . .
106
6.4. Test Kitchen
10 [ d e f a u l t ] Pre parin g network i n t e r f a c e s based on
c o n f i g u r a t i o n . . .
- [ d e f a u l t ] Forwarding p o r t s . . .
- [ d e f a u l t ] 22 => 2222 ( a dapter 1)
- [ d e f a u l t ] Running preboot VM custo m i z a t i o n s . . .
- [ d e f a u l t ] Booting VM. . .
15 [ d e f a u l t ] Waiting f o r machine to boot . This may take a few
minutes . . .
- [ d e f a u l t ] Machine booted and ready !
- [ d e f a u l t ] S e t t i n g hostname . . .
- Vagrant i n s t a n c e <d e f a u l t ubuntu 1204> created .
- Fi n i shed c r e a t i n g <d e f a u l t ubuntu 1204> (3m56. 1 1 s ) .
20 > Kitchen i s f i n i s h e d . (3m57 . 6 6 s )
If you are a Vagrant user then the line containing vagrant up −−noprovision
will look familiar. Let’s check the status of our instance now:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo Created
Running Kitchen Converge
Now let’s let Test Kitchen run it for us on our Ubuntu 12.04 instance:
Line 1 $ k i t c h e n conve rge d e f a u l t ubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > Converging <d e f a u l t ubuntu 1204 >...
- Prepa ring f i l e s f o r t r a n s f e r
5 R esol v ing cookbook depe n den c ies with B e r k s h e l f 2 . 0 . 1 4 . . .
- Removing noncookbook f i l e s b e f o r e t r a n s f e r
- T r a n s f e r i n g f i l e s t o <d e f a u l t ubuntu 1204>
- . . .
- F inish e d conv e rgin g <d e f a u l t ubuntu 1204> (10m24 . 1 3 s ) .
10 > Kitchen i s f i n i s h e d . (1 0m26.31 s )
To quote our Chef run, that was too easy. If you are a Chef user then part
of the output above should look familiar to you. Here’s what happened at a
high level:
Chef was installed on the instance by performing an Omnibus package
installation
Your Git cookbook files and a minimal Chef Solo configuration were built
and uploaded to the instance
A Chef run was initiated using the run-list and node attributes specified
in the .kitchen.yml file
There’s nothing to stop you from running this command again (or over-
and-over for that matter) so, let’s see what happens:
Line 1 $ k i t c h e n conve rge d e f a u l t ubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
107
6.4. Test Kitchen
- > Converging <d e f a u l t ubuntu 1204 >...
- Prepa ring f i l e s f o r t r a n s f e r
5 R esol v ing cookbook depe n den c ies with B e r k s h e l f 2 . 0 . 1 4 . . .
- Removing noncookbook f i l e s b e f o r e t r a n s f e r
- T r a n s f e r i n g f i l e s t o <d e f a u l t ubuntu 1204>
- . . .
- F inish e d conv e rgin g <d e f a u l t ubuntu 1204> (0m13. 1 3 s ) .
10 > Kitchen i s f i n i s h e d . (0m15 . 2 3 s )
That ran a lot faster didn’t it? Here’s what happened this time:
Test Kitchen found that Chef was present and installed so skipped a
re-installation.
The same Chef cookbook files and Chef Solo configuration was uploaded
to the instance. Test Kitchen is optimizing for freshness of code and
configuration over speed. Although we all like speed wherever possible.
A Chef run is initiated and runs very quickly as we are in the desired
state.
Let’s check the status of our instance:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo Converged
A clean converge run, success!
Manually Verifying
Test Kitchen has a login subcommand for just these kinds of situations:
Line 1 $ k i t c h e n l o g i n d e f a u l t ubuntu 1204
- Welcome to Ubuntu 1 2 . 0 4 . 3 LTS (GNU/ Linux 3.8.0 29 g e n e r i c x86_64 )
-
- * Documentation : h t tps : / / hel p . ubuntu . com/
5 Last l o g i n : Thu Mar 6 2 2 : 0 1 : 0 8 2014 from 1 0 . 0 . 2 . 2
- vagra nt @defaul tubuntu 1204:~ $ ps ax | grep nginx
- 6151 ? Ss 0 :00 nginx : master p r o c e s s / u sr / s b i n / nginx
- 26282 ? S 0:00 nginx : worker p r o c e s s
- 26758 pts /0 S+ 0 : 00 grep c o l o r=auto nginx
As you can see by the prompt above we are now in the «default-ubuntu-
1204» instance and nginx installed inside it.
Writing a Test
Now if you are interested in writing some tests, Test Kitchen has several
options available to you. The component that helps facilitate testing on your
instances is called Busser. Just like Test Kitchen it is a RubyGem library and
it provides a plugin system so that you can wire in whatever testing framework
your heart desires. A quick search on the RubyGems website returns several
testing frameworks currently available to you.
108
6.4. Test Kitchen
To keep things simple we’re going to use the busserbats runner plugin which
uses the Bash Automated Testing System also known as bats.
We need to put our test files in a specific location, so let’s create the
directory:
Line 1 $ mkdir p t e s t / i n t e g r a t i o n / d e f a u l t / b ats
It looks long and dense, but each directory has some meaning to Test
Kitchen and the Busser helper:
test/integration: Test Kitchen will look for tests to run under this di-
rectory. It allows you to put unit or other tests in test/unit, spec, ac-
ceptance, or wherever without mixing them up. This is configurable, if
desired.
default: This corresponds exactly to the Suite name we set up in the
.kitchen.yml file. If we had a suite called «server-only», then you would
put tests for the server only suite under test/integration/server-only.
bats: This tells Test Kitchen (and Busser) which Busser runner plugin
needs to be installed on the remote instance. In other words the bats
directory name will cause Busser to install busser-bats from RubyGems.
Let’s write a test. Create a new file called test/integration/default/bats/
nginx_installed.bats with the following:
Line 1 #! / u sr / bin / env bat s
-
- @test " nginx bina ry i s found i n PATH" {
- run which nginx
5 [ " $ s t a t u s " eq 0 ]
- }
Now to put our test to the test. For this we’ll use the verify subcommand:
Line 1 $ k i t c h e n v e r i f y d e f a u l t ubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > S e t t i n g up <d e f a u l t ubuntu 1204 >...
- Fet ching : thor 0. 1 8 . 1 . gem (100%)
5 Fet ching : busse r 0 . 6 . 0 . gem (100%)
- . . .
- Uploading /tmp/ buss e r / s u i t e s / ba ts / n g i n x _ i n s t a l l e d . b ats ( mode=0755)
- > Running b ats t e s t s u i t e
- nginx b ina ry i s found i n PATH
10
- 1 test , 0 f a i l u r e s
- F inish e d v e r i f y i n g <d e f a u l t ubuntu 1204> (0m1. 3 9 s ) .
- > Kitchen i s f i n i s h e d . (0m16 . 5 1 s )
All right!
A few things of note from the output above:
Notice the Setting up «default-ubuntu-1204» line? That’s another action
called the Setup Action. Usually not a big for most users but this action
109
6.4. Test Kitchen
is responsible for installing the Busser RubyGem and any test runner
plugins required. In our case the busser-bats RubyGem was installed
which helped to install the bats testing framework.
The Verifying «default-ubuntu-1204» line corresponds to the start of the
Verify Action and the nginx binary is found in PATH line is output from
a bats test run.
The last command (echo \$?) is a way to print the exit code of the last run
shell command. This shows that the kitchen command exited cleanly.
Let’s check the status of our instance again:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo V e r i f i e d
Let’s add more tests:
Line 1 @test " nginx c o n f i g i s v a l i d " {
- run sudo nginx t
- [ " $ s t a t u s " eq 0 ]
- }
5
- @test " nginx i s running " {
- run s e r v i c e nginx s t a t u s
- [ " $ s t a t u s " eq 0 ]
- [ " $output " == " * nginx i s ru nning " ]
10 }
Running Kitchen Test
Now it’s time to introduce to the test meta-action which helps you automate
all the previous actions so far into one command. Recall that we currently have
our instance in a «verified» state. With this in mind, let’s run kitchen test:
Line 1 $ k i t c h e n t e s t d e f a u l t ubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > C le aning up any p r i o r i n s t a n c e s o f <d e f a u l t ubuntu1204>
- > Des tro yin g <d e f a u l t ubuntu 1204 >...
5 F inish e d d e s t r o y i n g <d e f a u l t ubuntu 1204> (0m0. 0 0 s ) .
- > Tes tin g <d e f a u l t ubuntu 1204>
- . . .
- Uploading /tmp/ buss e r / s u i t e s / ba ts / n g i n x _ i n s t a l l e d . b ats ( mode=0755)
- > Running b ats t e s t s u i t e
10 nginx b ina ry i s found i n PATH
-
- 1 test , 0 f a i l u r e s
- F inish e d v e r i f y i n g <d e f a u l t ubuntu 1204> (0m1. 3 9 s ) .
- > Kitchen i s f i n i s h e d . (0m16 . 5 1 s )
There’s only one remaining action left that needs a mention: the Destroy
Action which destroys the instance. With this in mind, here’s what Test
Kitchen is doing in the Test Action:
110
6.4. Test Kitchen
Destroys the instance if it exists (Cleaning up any prior instances of <default
ubuntu1204>)
Creates the instance (Creating <defaultubuntu1204>)
Converges the instance (Converging <defaultubuntu1204>)
Sets up Busser and runner plugins on the instance (Setting up <default
ubuntu1204>)
Verifies the instance by running Busser tests (Verifying <defaultubuntu
1204>)
Destroys the instance (Destroying <defaultubuntu1204>)
A few details with regards to test:
Test Kitchen will abort the run on the instance at the first sign of trouble.
This means that if your Chef run fails then Busser won’t be installed and
the instance won’t be destroyed. This gives you a chance to inspect the
state of the instance and fix any issues.
The behavior of the final destroy action can be overridden if desired.
Check out the documentation for the −−destroy flag using kitchen help
test.
The primary use case in mind for this meta-action is in a Continuous In-
tegration environment or a command for developers to run before check
in or after a fresh clone. If you’re using this in your test-code-verify
development cycle it’s going to quickly become very slow and frustrat-
ing. You’re better off running the converge and verify subcommands in
development and save the test subcommand when you need to verify the
end-to-end run of your code.
Finally, let’s check the status of the instance:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo <Not Created>
Adding a Platform
Now that we are masters of the Ubuntu platform, let’s add support for
CentOS to our cookbook. This shouldn’t be too bad. Open .kitchen.yml in
your editor and the centos6.4 line to your platforms list so that it resembles:
Line 1
- d r i v e r :
- name : vagrant
-
5 p r o v i s i o n e r :
- name : c h ef_sol o
-
- platform s :
- name : ubuntu 12.04
111
6.4. Test Kitchen
10 name : cen to s 6.4
-
- s u i t e s :
- name : d e f a u l t
- r u n _ l i s t :
15 r e c i p e [ my_cool_app : : d e f a u l t ]
- a t t r i b u t e s :
Now let’s check the status of our instances:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant Chef Sol o <Not Created>
- d e f a u l t c entos 64 Vagrant Chef Solo <Not Created>
We’re going to use two shortcuts here in the next command:
Each Test Kitchen instance has a simple state machine that tracks where
it is in its lifecycle. Given its current state and the desired state, the in-
stance is smart enough to run all actions in between current and desired.
In our next example we will take an instance from not created to verified
in one command.
Any kitchen subcommand that takes an instance name as an argument can
take a Ruby regular expression that will be used to glob a list of instances
together. This means that kitchen test ubuntu would run the test action
only the ubuntu instance. Note that the list subcommand also takes the
regex-globbing argument so feel free to experiment there. In our next
example we’ll select the defaultcentos64 instance with simply 64.
Let’s see how CentOS runs our cookbook:
Line 1 $ k i t c h e n v e r i f y 64
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > Cre ati ng <d e f a u l t cen tos 64 >...
- . . .
5 > Running b ats t e s t s u i t e
- nginx bin ary i s found i n PATH
- nginx c o n f i g i s v a l i d
2/3
- nginx c o n f i g i s v a l i d
- nginx i s running
3/3
10 nginx i s running
- ( in t e s t f i l e /tmp/ busse r / s u i t e s / bat s / n g i n x _ i n s t a l l e d .
bats , l i n e 16)
-
- 3 t e s t s , 1 f a i l u r e
We should fix failed test:
Line 1 @test " nginx i s running " {
- run s e r v i c e nginx s t a t u s
- [ " $ s t a t u s " eq 0 ]
- [ " $output " == " * nginx i s running " ]
112
6.4. Test Kitchen
5 + [ $ ( expr " $output " : " . * nginx . * runn ing " ) ne 0 ]
- }
And check again on all instances:
Line 1 $ k i t c h e n v e r i f y
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > Cre ati ng <d e f a u l t cen tos 64 >...
- . . .
5 > V e r i f y i n g <d e f a u l t ubuntu 1204 >...
- S u i t e path d i r e c t o r y /tmp/ buss e r / s u i t e s does not e x i s t ,
s k i p p i n g .
- Uploading /tmp/ buss e r / s u i t e s / ba ts / n g i n x _ i n s t a l l e d . b ats ( mode=0755)
- > Running b ats t e s t s u i t e
- nginx bin ary i s found i n PATH
10 nginx c o n f i g i s v a l i d
- nginx i s running
-
- 3 t e s t s , 0 f a i l u r e s
- F inish e d v e r i f y i n g <d e f a u l t ubuntu 1204> (0m1. 4 6 s ) .
15 > V e r i f y i n g <d e f a u l t c en tos 64 >...
- Removing /tmp/ busse r / s u i t e s / bat s
- Uploading /tmp/ b u sser / s u i t e s / b ats / n g i n x _ i n s t a l l e d . b ats (
mode=0755)
- > Running b ats t e s t s u i t e
- nginx bin ary i s found i n PATH
20 nginx c o n f i g i s v a l i d
2/3
- nginx c o n f i g i s v a l i d
- nginx i s running
3/3
- nginx i s running
-
25 3 t e s t s , 0 f a i l u r e s
- F inish e d v e r i f y i n g <d e f a u l t c entos 64> (0m1. 8 1 s ) .
Nice! We’ve verified that our cookbook works on Ubuntu 12.04 and CentOS
6.4. Since the CentOS instance will hang out for no good reason, let’s kill it
for now:
Line 1 $ k i t c h e n d e s t r o y
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > Des tro yin g <d e f a u l t ubuntu 1204 >...
- . . .
5 F inish e d d e s t r o y i n g <d e f a u l t ubuntu 1204> (0m5. 8 2 s ) .
- > Des tro yin g <d e f a u l t cen tos 64 >...
- . . .
- F inish e d d e s t r o y i n g <d e f a u l t c en tos 64> (0m5. 4 1 s ) .
- > Kitchen i s f i n i s h e d . (0m12 . 9 5 s )
Any kitchen subcommand without an instance argument will apply to all
instances.
113
6.4. Test Kitchen
Fixing Converge
Now a colleague has expressed interest in running the cookbook on a fleet
of older Ubuntu 10.04 systems. Open .kitchen.yml in your editor and add a
ubuntu10.04 entry to the platforms list:
Line 1
- d r i v e r :
- name : vagrant
-
5 p r o v i s i o n e r :
- name : c h ef_sol o
-
- platform s :
- name : ubuntu 12.04
10 name : ubuntu 10.04
- name : cen to s 6.4
-
- s u i t e s :
- name : d e f a u l t
15 r u n _ l i s t :
- r e c i p e [ my_cool_app : : d e f a u l t ]
- a t t r i b u t e s :
And run kitchen list to confirm the introduction of our latest instance:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo <Not Created>
- d e f a u l t ubuntu 1004 Vagrant ChefSolo <Not Created>
5 d e f a u l t c entos 64 Vagrant ChefSolo <Not Created>
Now we’ll run the test subcommand and go grab a coffee:
Line 1 $ k i t c h e n t e s t 10
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > C le aning up any p r i o r i n s t a n c e s o f <d e f a u l t ubuntu1004>
- > Des tro yin g <d e f a u l t ubuntu 1004 >...
5 F inish e d d e s t r o y i n g <d e f a u l t ubuntu 1004> (0m0. 0 0 s ) .
- > Tes tin g <d e f a u l t ubuntu 1004>
- . . .
- Chef : : Ex cep tions : : Package
-
10 g i t has no c a ndid a te i n the aptcach e
Oh noes! Argh, why!? Let’s login to the instance and see if we can figure
out what the correct package is:
Line 1 $ k i t c h en l o g i n 10
-
- vagra nt @defaul tubuntu 1004:~ $ sudo aptcach e s e arch g i t | grep ^
g i t
- git bui l dpa c kag e S u i t e to h el p with Debian p ackages in Git
r e p o s i t o r i e s
5 git c o l a h i g h l y c a f f e i n a t e d g i t g u i
- git load d i r s Import upstream a r c h i v e s i n t o g i t
114
6.4. Test Kitchen
- g i t g g i t r e p o s i t o r y v iew er f o r gtk+/GNOME
- g i t magi c gui de about Git v e r s i o n c o n t r o l system
- g i t o s i s g i t r e p o s i t o r y h o s t i n g a p p l i c a t i o n
10 g i t p kg t o o l s f o r m ain tai n ing Debian packages with g i t
- g i t s t a t s s t a t i s t i c s g e n e r a t o r f o r g i t r e p o s i t o r i e s
- git c o r e f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system
- git doc f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system (
documentation )
- g i t k f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system (
r e v i s i o n t r e e v i s u a l i z e r )
15 git arch f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system (
arch i n t e r o p e r a b i l i t y )
- git c vs f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system ( c vs
i n t e r o p e r a b i l i t y )
- git daemonrun f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l
system ( g i t daemon s e r v i c e )
- git email fa s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system (
email addon )
- git gui f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system (GUI
)
20 git svn f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system ( svn
i n t e r o p e r a b i l i t y )
- gitweb f a s t , s c a l a b l e , d i s t r i b u t e d r e v i s i o n c o n t r o l system ( web
i n t e r f a c e )
Okay, it looks like we want to install the gitcore package for this release of
Ubuntu. Let’s fix this up back in the default recipe. Open up recipes/default .rb
and edit to something like:
Line 1 # i n s t a l l needed package
- pack ages = %w( ntp )
-
- i f " ubuntu " == node [ plat form ] && node [ p lat f orm_ v ersi o n ] . to_f
<= 1 0.04
5 packages << " g i t c ore "
- e l s e
- packages << " g i t "
- end
-
10 pack ages . each do | pack |
- package pack
- end
This may not be pretty but let’s verify that it works first on Ubuntu 10.04:
Line 1 $ k i t c h e n v e r i f y 10
- . . .
- > Running b ats t e s t s u i t e
- nginx bin ary i s found i n PATH
5 nginx c o n f i g i s v a l i d
2/3
- nginx c o n f i g i s v a l i d
- nginx i s running
3/3
- nginx i s running
115
6.4. Test Kitchen
-
10 3 t e s t s , 0 f a i l u r e s
- F inish e d v e r i f y i n g <d e f a u l t ubuntu 1004> (0m2. 5 1 s ) .
- > Kitchen i s f i n i s h e d . (0m53 . 4 8 s )
Back to green, good. Let’s verify that the all instances are still good.
Line 1 $ k i t c h e n v e r i f y c 3
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > V e r i f y i n g <d e f a u l t c en tos 64 >...
- > V e r i f y i n g <d e f a u l t ubuntu 1004 >...
5 > V e r i f y i n g <d e f a u l t ubuntu 1204 >...
- Removing /tmp/ busse r / s u i t e s / bat s
- Removing /tmp/ busse r / s u i t e s / bat s
- Uploading /tmp/ b u sser / s u i t e s / b ats / n g i n x _ i n s t a l l e d . b ats (
mode=0755)
- Uploading /tmp/ buss e r / s u i t e s / ba ts / n g i n x _ i n s t a l l e d . b ats ( mode=0755)
10 Removing /tmp/ busse r / s u i t e s / bat s
- Uploading /tmp/ b u sser / s u i t e s / b ats / n g i n x _ i n s t a l l e d . b ats (
mode=0755)
- > Running b ats t e s t s u i t e
- > Running ba ts t e s t s u i t e
1/3
- nginx bin ary i s found i n PATH
15 nginx c o n f i g i s v a l i d
2/3
- nginx c o n f i g i s v a l i d
- nginx i s running
3/3
- nginx i s running
-
20 3 t e s t s , 0 f a i l u r e s
- F inis h e d v e r i f y i n g <d e f a u l t ubuntu1004> (0m2.94 s ) .
1/3
- nginx bin ary i s found i n PATH
- nginx c o n f i g i s v a l i d
- nginx i s running
25
- 3 t e s t s , 0 f a i l u r e s
- F inish e d v e r i f y i n g <d e f a u l t ubuntu 1204> (0m3. 0 9 s ) .
- > Running b ats t e s t s u i t e
- nginx bin ary i s found i n PATH
30 nginx c o n f i g i s v a l i d
2/3
- nginx c o n f i g i s v a l i d
- nginx i s running
3/3
- nginx i s running
-
35 3 t e s t s , 0 f a i l u r e s
- F inish e d v e r i f y i n g <d e f a u l t c entos 64> (0m3. 8 6 s ) .
- > Kitchen i s f i n i s h e d . (0m6.12 s )
We used c to run a test against all matching instances concurrently, where
next argument mean number of instances run at the same time.
116
6.4. Test Kitchen
We’ve successfully verified all three instances, so let’s shut them down.
Line 1 $ k i t c h e n d e s t r o y
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- . . .
- > Kitchen i s f i n i s h e d . (0m19 . 8 6 s )
Adding a Suite
We’re going to call our new Test Kitchen Suite «node» by opening .kitchen
.yml in your editor of choice so that it looks similar to:
Line 1
- d r i v e r :
- name : vagrant
-
5 p r o v i s i o n e r :
- name : c h ef_sol o
-
- platform s :
- name : ubuntu 12.04
10 name : ubuntu 10.04
- name : cen to s 6.4
-
- s u i t e s :
- name : d e f a u l t
15 r u n _ l i s t :
- r e c i p e [ my_cool_app : : d e f a u l t ]
- a t t r i b u t e s :
- name : node
- r u n _ l i s t :
20 r e c i p e [ my_cool_app : : d e f a u l t ]
- r e c i p e [ my_cool_app : : node ]
- a t t r i b u t e s :
Now run kitchen list to see our new suite in action:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo <Not Created>
- d e f a u l t ubuntu 1004 Vagrant ChefSolo <Not Created>
5 d e f a u l t c entos 64 Vagrant ChefSolo <Not Created>
- nodeubuntu 1204 Vagrant ChefSolo <Not Created>
- nodeubuntu 1004 Vagrant ChefSolo <Not Created>
- nodece ntos 64 Vagrant ChefSolo <Not Created>
Writing a Server Test
Now to write a test or two. Previously we’ve seen the bats testing frame-
work in action but this isn’t always a viable option. For example if you needed
to verify that a package was installed and you needed to test that on Ubuntu
and CentOS platforms, then what would you do? You need to bust out some
117
6.4. Test Kitchen
platform detection in order to run a Debian or RPM-based command. Feels
like Chef would help us here since it’s good at that sort of thing. On the
other hand there are advantages to treating our Chef run as a black box - a
configuration-management implementation detail, if you will. So what to do?
A nice solution to the platform-agnostic test issue exists called ServerSpec.
It is a set of RSpec matchers that can assert things about servers like packages
installed, services enabled, ports listening, etc. Let’s see what this looks like
for our Git Daemon tests.
First we’re going to create a directory for our test file:
Line 1 $ mkdir p t e s t / i n t e g r a t i o n / node/ s e r v e r s p e c
Next, create a file called test/integration/server/serverspec/nginx_daemon_spec.
rb with the following:
Line 1 r e q u i r e s e r v e r s p e c
-
- i n c l u d e S e r v e r s p e c : : He lp er : : Exec
- i n c l u d e S e r v e r s p e c : : He lp er : : DetectOS
5
- RSpec . c o n f i g u r e do | c |
- c . b e f o r e : a l l do
- c . path = / s bin : / usr / s b i n
- end
10 end
-
- d e s c r i b e " Nginx Daemon" do
-
- i t " i s l i s t e n i n g on p ort 80 " do
15 exp ect ( p ort ( 8 0 ) ) . to b e _ l i s t e n i n g
- end
-
- i t " has a running s e r v i c e o f g i t daemon " do
- exp ect ( s e r v i c e ( " nginx " ) ) . t o be_running
20 end
-
- end
And test/integration/server/serverspec/node_spec.rb with the following:
Line 1 r e q u i r e s e r v e r s p e c
-
- i n c l u d e S e r v e r s p e c : : He lp er : : Exec
- i n c l u d e S e r v e r s p e c : : He lp er : : DetectOS
5
- RSpec . c o n f i g u r e do | c |
- c . b e f o r e : a l l do
- c . path = / s bin : / usr / s b i n : / u sr / l o c a l / bin
- end
10 end
-
- d e s c r i b e " Node " do
-
- d e s c r i b e command( node v ) do
118
6.4. Test Kitchen
15 i t { sho ul d retur n_std out v0 . 1 0 . 2 6 }
- end
-
- end
As our primary target platform was Ubuntu 12.04, we’ll target this one first
for development. Now, in Test-Driven style we’ll run kitchen verify to watch our
tests fail spectacularly:
Line 1 $ k i t c h e n v e r i f y nodeubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > Cre ati ng <nodeubuntu 1204 >...
- . . .
5
- Nginx Daemon
- i s l i s t e n i n g on p ort 80
- has a running s e r v i c e o f g i t daemon
-
10 Node
- Command " node v "
-
- sh ould re turn stdo ut " v0 . 1 0 . 2 6 "
-
15 F i n ished i n 0.22 341 sec onds
- 3 examples , 0 f a i l u r e s
- F inish e d v e r i f y i n g <nodeubuntu 1204> (0m2. 7 8 s ) .
- > Kitchen i s f i n i s h e d . (1 3m29.49 s )
One quick check of kitchen list tells us that our instance was created by not
successfully converged:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo <Not Created>
- d e f a u l t ubuntu 1004 Vagrant ChefSolo <Not Created>
5 d e f a u l t c entos 64 Vagrant ChefSolo <Not Created>
- nodeubuntu 1204 Vagrant ChefSolo V e r i f i e d
- nodeubuntu 1004 Vagrant ChefSolo <Not Created>
- nodece ntos 64 Vagrant ChefSolo <Not Created>
Yes, you can specify one or more instances with the same Ruby regular
expression globbing as any other kitchen subcommands. Let’s see if our server
recipe works on the all platforms (Ubuntu 10.04 and CentOS 6.4). Fingers
crossed, here we go:
Line 1 $ k i t c h e n v e r i f y node
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > V e r i f y i n g <nodeubuntu 1204 >...
- . . .
5
- Nginx Daemon
- i s l i s t e n i n g on p ort 80
- has a running s e r v i c e o f g i t daemon
-
10 Node
119
6.4. Test Kitchen
- Command " node v "
- sh ould re turn stdo ut " v0 . 1 0 . 2 6 "
-
- F i n ished i n 0.09 353 sec onds
15 3 examples , 0 f a i l u r e s
- F inish e d v e r i f y i n g <nodeubuntu 1204> (0m2. 4 1 s ) .
- > V e r i f y i n g <nodeubuntu 1004 >...
- . . .
-
20 Nginx Daemon
- i s l i s t e n i n g on p ort 80
- has a running s e r v i c e o f g i t daemon
-
- Node
25 Command " node v "
- should re tur n stdo u t " v0 . 1 0 . 2 6 "
-
- F inish e d i n 0 .087 32 sec ond s
- 3 examples , 0 f a i l u r e s
30 F inish e d v e r i f y i n g <nodeubuntu 1004> (0m2. 3 4 s ) .
- > V e r i f y i n g <nodecento s 64 >...
- . . .
- Nginx Daemon
- i s l i s t e n i n g on p ort 80
35 has a running s e r v i c e o f g i t daemon
-
- Node
- Command " node v "
- should re tur n stdo u t " v0 . 1 0 . 2 6 "
40
- F inish e d i n 0 .127 61 sec ond s
- 3 examples , 0 f a i l u r e s
- F inish e d v e r i f y i n g <nodecen tos 64> (0m3. 1 1 s ) .
- > Kitchen i s f i n i s h e d . (0m9.27 s )
If for example, you don’t want support some platform, you can use excludes
key in .kitchen.yml file:
Line 1
- d r i v e r :
- name : vagrant
-
5 p r o v i s i o n e r :
- name : c h ef_sol o
-
- platform s :
- name : ubuntu 12.04
10 name : ubuntu 10.04
- name : cen to s 6.4
-
- s u i t e s :
- name : d e f a u l t
15 r u n _ l i s t :
- r e c i p e [ my_cool_app : : d e f a u l t ]
120
6.5. Chef Zero
- a t t r i b u t e s :
- name : node
- r u n _ l i s t :
20 r e c i p e [ my_cool_app : : d e f a u l t ]
- r e c i p e [ my_cool_app : : node ]
- a t t r i b u t e s :
- e x c l u d e s :
- c entos 6.4
Now let’s run kitchen list to ensure the instance is gone:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefSolo <Not Created>
- d e f a u l t ubuntu 1004 Vagrant ChefSolo <Not Created>
5 d e f a u l t c entos 64 Vagrant ChefSolo <Not Created>
- nodeubuntu 1204 Vagrant ChefSolo V e r i f i e d
- nodeubuntu 1004 Vagrant ChefSolo V e r i f i e d
6.5 Chef Zero
Chef Zero is a simple, easy-install, in-memory Chef server that can be useful
for Chef Client testing and chef-solo-like tasks that require a full Chef Server.
It IS intended to be simple, Chef 11 compliant, easy to run and fast to start.
It is NOT intended to be secure, scalable, performant or persistent. It does
NO input validation, authentication or authorization (it will not throw a 400,
401 or 403). It does not save data, and will start up empty each time you start
it.
Because Chef Zero runs in memory, it’s super fast and lightweight. This
makes it perfect for testing against a «real» Chef Server without mocking the
entire Internet.
Installing
Let’s cover our cookbook by using Chef Zero. First we should add this gem
in Gemfile:
Line 1 s ource h ttps : / / rubygems . org
-
- gem b e r k s h e l f
- gem f o o d c r i t i c
5 gem thor f o o d c r i t i c
- gem c h e f s p e c
- gem t e s t k i t c h e n
- gem k itc hen vagrant
- gem c hef z e r o
And you should to execute bundle command to install this gem. We can
check what Chef Zero installed:
121
6.5. Chef Zero
Line 1 $ chefzero
- >> S t a r t i n g Chef Zero ( v1 . 7 . 3 ) . . .
- >> Puma ( v1 . 6 . 3 ) i s l i s t e n i n g at ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
- >> Pr e ss CTRL+C to s top
5 ^C
- >> Stopping Chef Zero . . .
Command chefzero start Chef Zero server in foreground. This is fully
functional (empty) Chef Server. To use it in your own repository, create a
knife .rb like so:
Line 1 chef _ s erver _ u rl http : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
- node_name s t i c k y w i c k e t
- clien t _ k e y path_to_any_pem_file . pem
And use knife like you normally would.
Since Chef Zero does no authentication, any .pem file will do. The client just
needs something to sign requests with (which will be ignored on the server).
Even though it’s ignored, the .pem must still be a valid format.
If you will stop the Chef Zero server than all the data is gone.
Run chefzero −−help to see a list of the supported flags and options:
Line 1 $ chefzero h elp
- Usage : c hef z e r o [ARGS]
- H, h ost HOST Host t o bind to ( d e f a u l t :
1 2 7 . 0 . 0 . 1 )
- p , p ort PORT Port to l i s t e n on
5 s o c k e t PATH Unix s o c k e t path to l i s t e n on
- −−[no ] g ene rat e k eys Whether t o g e n e r a te a c t u a l
keys or f a k e i t ( f a s t e r ) . D e f ault : f a l s e .
- d , daemon Run as a daemon p r o c e s s
- l , log l e v e l LEVEL Set the output l o g l e v e l
- h , h el p Show t h i s message
10 v e r s i o n Show v e r s i o n
Using with ChefSpec
By default, ChefSpec runs in Chef Solo mode, but you can ask ChefSpec
to create an in-memory Chef Server during testing using ChefZero. This is
especially helpful if you need to support searching or data bags.
To use the ChefSpec server, simply require the module in your spec_helper:
Line 1 # s pec _he lper . rb
- r e q u i r e c h e f s p e c
- r e q u i r e c h e f s p e c / s e r v e r
This will automatically create a Chef server, synchronize all the cookbooks
in your cookbook_path, and wire all the internals of Chef together. Recipe calls
to search, data_bag and data_bag_item will now query the ChefSpec server.
The ChefSpec server includes a collection of helpful DSL methods for pop-
ulating data into the Chef Server.
Create a client:
122
6.5. Chef Zero
Line 1 ChefSpec : : S erve r . c r e a t e _ c l i e n t ( my_client , { admin : t rue })
Create a data bag (and items):
Line 1 ChefSpec : : S erve r . create_data_bag ( my_data_bag , {
- item_1 => {
- password => abc123
- } ,
5 item_2 => {
- password => d ef4 56
- }
- })
Create an environment:
Line 1 ChefSpec : : S erve r . create_e nvironmen t ( my_environment , {
d e s c r i p t i o n : . . . })
Create a node:
Line 1 ChefSpec : : S erve r . create_node ( my_node , { r u n _ l i s t : [ . . . ] })
You may also be interested in the stub_node macro, which will create a new
Chef::Node object and accepts the same parameters as the Chef Runner and a
Fauxhai object:
Line 1 www = stub_node ( pla t for m : ubuntu , v e r s i o n : 1 2.04 ) do | node |
- node . s e t [ a t t r i b u t e ] = val u e
- end
-
5 # www i s now a l o c a l Chef : : Node o b j e c t you can use in your t e s t .
To push t h i s
- # node to the s e r v e r , c a l l create_node :
-
- ChefSpec : : S erver . create_node (www)
Create a role:
Line 1 ChefSpec : : S erve r . c r e a t e _ r o l e ( my_role , { d e f a u l t _ a t t r i b u t e s : {}
})
-
- # The r o l e now e x i s t s on the Chef Server , you can add i t to a node
s r u n _ l i s t
- # by adding i t to the converge b lock :
5 l e t ( : chef_run ) { ChefSpec : : Runner . new . conve rge ( d e s c r i b ed_rec i p e ,
r o l e [ my_role ] ) }
Example
Let’s create recipe haproxy in our cookbook:
Line 1 # i n s t a l l and set up haproxy
-
- package " haproxy "
-
5 # g et nodes o f some r o l e s
- i f Chef : : Co nf ig [ : s o l o ]
123
6.5. Chef Zero
- Chef : : Log . warn ( " This r e c i p e uses s e a r ch . Chef Sol o does not
su pport s e a r c h . " )
- pool_members = [ ]
- e l s e
10 pool_members = s e a r c h ( " node " , " r o l e :#{ node [ my_cool_app ] [
haproxy ] [ ap p_ se rv er _r ole ] } AND chef_environment :#{node .
chef_environment } " ) | | [ ]
- end
-
- pool_members . map ! do | member |
- {: i p a d d r e s s => member [ i p a d d r e s s ] , : hostname => member [
hostname ] }
15 end
-
- i f pool_members . l e n g t h > 0
-
- h t t p _ c l i e n t s = pool_members . uniq . map do | s |
20 " s e r v e r #{s [ : hostname ] } #{s [ : i p a d d r e s s ] } : 8 0 we ight 1 maxconn
1024 check "
- end
- h t t p _ c l i e n t s = [ " mode http " ] + h t t p _ c l i e n t s + [ " o ptio n httpchk
GET / heal t h c heck " ]
-
- ht t p s _ c l i e n t s = pool_members . uniq . map do | s |
25 " s e r v e r #{s [ : hostname ] } #{s [ : i p a d d r e s s ] } : 4 4 3 weight 1 maxconn
1024 check "
- end
- ht t p s _ c l i e n t s = [ " mode htt p " ] + h t t p s _ c l i e n t s + [ " o ptio n httpchk
GET / s s l h e a l thche c k " ]
-
- e l s e
30
- h t t p _ c l i e n t s = h t t p s _ c l i e n t s = [ ]
-
- end
-
35 l i s t e n e r s = {
- " l i s t e n " => {} ,
- " f r o n tend " => {
- " ft_http " => [
- " bind * : 8 0 " ,
40 " mode tcp " ,
- " default _backen d bk_http "
- ] ,
- " f t_htt p s " => [
- " bind * : 443 " ,
45 " mode tcp " ,
- " default _backen d bk_https "
- ]
- } ,
- " backend " => {
50 " bk_http " => h t t p _ c l i e n t s ,
- " bk_https " => h t t p s _ c l i e n t s
124
6.5. Chef Zero
- }
- }
-
55 templ at e " / e t c / haproxy / haproxy . c f g " do
- s o u r ce " haproxy . c f g . erb "
- owner " r o ot "
- group " r oot "
- mode 00644
60 n o t i f i e s : relo ad , " s e r v i c e [ haproxy ] "
- v a r i a b l e s (
- : l i s t e n e r s => l i s t e n e r s
- )
- end
65
- c o o kbook _ f ile / et c / d e f a u l t / haproxy do
- s o u r ce haproxy d e f a u l t
- owner r o o t
- group r o o t
70 mode 00644
- n o t i f i e s : r e s t a r t , s e r v i c e [ haproxy ]
- end
-
- s e r v i c e " haproxy " do
75 supp o rts : r e s t a r t => true , : s t a t u s => true , : r e l o a d => t rue
- a c t i o n [ : enable , : s t a r t ]
- end
And default attributes for it:
Line 1 d e f a u l t [ my_cool_app ] [ haproxy ] [ app_se rv er _r ol e ] = web
In our recipe used template haproxy.cfg.erb:
Line 1 g l o b a l
- l o g 1 2 7 . 0 . 0 . 1 l o c a l 0
- l o g 1 2 7 . 0 . 0 . 1 l o c a l 1 n o t i c e
- maxconn 5000
5 u s er haproxy
- group haproxy
-
- d e f a u l t s
- l o g g l o b a l
10 mode http
- r e t r i e s 3
- timeout c l i e n t 60000
- timeout s e r v e r 50000
- timeout c onnect 25000
15 bal ance roundrobin
-
- <% @ l i s t e n e r s . each do | type , l i s t e n e r s | %>
- <% l i s t e n e r s . each do | name , l i s t e n | %>
- <%= type %> <%= name %>
20 <% l i s t e n . each do | o pti o n | %>
- <%= o ptio n %>
- <% end %>
125
6.5. Chef Zero
- <% end %>
- <% end %>
As you can see, this recipe install and configure haproxy. It is using search
command to get all nodes with role web (by default attributes) and chef
environment. After this it collects from all selected nodes hostname and ip
address. This data is used to create config for haproxy. This is very useful
way, because if you add or remove node with role web from Chef server, it
will automatically update haproxy config. But because we are using search
command, we need test our cookbook with Chef Zero. Here how it looks:
Line 1 r e q u i r e sp ec_ hel per
-
- d e s c r i b e my_cool_app : : haproxy do
- l e t ( : pla t for m ) { ubuntu }
5 l e t ( : plat f orm_ v ers i on ) { 1 2.04 }
- l e t ( : chef_run ) { ChefSpec : : Runner . new ( p l atf o rm : platform ,
v e r s i o n : p l atfo r m_v e rsio n ) . con verge ( d e s c r i b e d _ r e c i p e ) }
-
- i t " i n s t a l l haproxy package " do
- exp ect ( chef_run ) . to i n s t a l l _ p a c k a g e ( haproxy )
10 end
-
- i t e nab l e haproxy s e r v i c e do
- exp ect ( chef_run ) . to e n a b l e _ s e r v i c e ( haproxy )
- end
15
- i t c r e a t e c o n f i g / e t c / haproxy / haproxy . c f g with empty backends
do
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( / e t c / haproxy / haproxy . c f g ) .
with_content (/#{Regexp . quote ( " backend bk_http \ nbackend
bk_https \n " ) } /)
- end
-
20 cont e xt with env , r o l e and one node do
- l e t ( : node_env ) { t e s t }
- l e t ( : chef_run ) do
- ChefSpec : : Runner . new ( p l atf o rm : platform , v e r s i o n :
plat form _ vers i on ) do | node |
- # Create a new environment ( you cou ld a l s o use a d i f f e r e n t
: l e t bloc k or : b e f o r e b lock )
25 env = Chef : : Environment . new
- env . name node_env
-
- # Stub the node to retur n t h i s environment
- node . stub ( : chef_environment ) . and_return ( env . name)
30
- # Stub any c a l l s to Environment . loa d to r e turn t h i s
environment
- Chef : : Environment . stub ( : l oad ) . and_return ( env )
- end . con verge ( d e s c r i b e d _ r e c i p e )
- end
35
126
6.5. Chef Zero
- b e f o r e do
- ChefSpec : : S erve r . create_environment ( node_env , { d e s c r i p t i o n :
Test env })
- ChefSpec : : S erve r . c r e a t e _ r o l e ( web , { d e f a u l t _ a t t r i b u t e s : {}
})
- ChefSpec : : S erve r . create_node ( f i r s t node , {
40 r u n _ l i s t : [ r o l e [ web ] ] ,
- chef_environment : node_env ,
- normal : { fqdn : 1 2 7 . 0 . 0 . 1 , hostname : t e s t . org ,
i p a d d r e s s : 1 2 7 . 0 . 0 . 1 }
- })
- end
45
- i t c r e a t e c o n f i g / e t c / haproxy / haproxy . c f g with f i r s t node on
80 por t do
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( / e t c / haproxy / haproxy . c f g ) .
with_content (/#{Regexp . quote ( s e r v e r t e s t . org
1 2 7 . 0 . 0 . 1 : 8 0 weight 1 maxconn 1024 check ) }/)
- end
-
50 i t c r e a t e c o n f i g / e t c / haproxy / haproxy . c f g with f i r s t node on
443 p ort do
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( / e t c / haproxy / haproxy . c f g ) .
with_content (/#{Regexp . quote ( s e r v e r t e s t . org
1 2 7 . 0 . 0 . 1 : 4 4 3 weight 1 maxconn 1024 check ) }/)
- end
-
- cont e xt with two nodes do
55 b e f o r e do
- ChefSpec : : S e rver . create_node ( secondnode , {
- r u n _ l i s t : [ r o l e [ web ] ] ,
- chef_environment : node_env ,
- normal : { fqdn : 1 9 2 . 1 6 8 . 1 . 2 , hostname : t e s t 2 . org ,
i p a d d r e s s : 1 9 2 . 1 6 8 . 1 . 2 }
60 })
- end
-
- i t c r e a t e c o n f i g / e t c /haproxy / haproxy . c f g with f i r s t node
on 80 p ort do
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( / e t c / haproxy / haproxy . c f g
) . with_content (/#{Regexp . quote ( s e r v e r t e s t . org
1 2 7 . 0 . 0 . 1 : 8 0 weight 1 maxconn 1024 check ) }/)
65 end
-
- i t c r e a t e c o n f i g / e t c /haproxy / haproxy . c f g with f i r s t node
on 443 por t do
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( / e t c / haproxy / haproxy . c f g
) . with_content (/#{Regexp . quote ( s e r v e r t e s t . org
1 2 7 . 0 . 0 . 1 : 4 4 3 weight 1 maxconn 1024 check ) }/)
- end
70
- i t c r e a t e c o n f i g / e t c /haproxy / haproxy . c f g with secondnode
on 80 p ort do
127
6.5. Chef Zero
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( / e t c / haproxy / haproxy . c f g
) . with_content (/#{Regexp . quote ( s e r v e r t e s t 2 . org
1 9 2 . 1 6 8 . 1 . 2 : 8 0 weight 1 maxconn 1024 check ) }/)
- end
-
75 i t c r e a t e c o n f i g / e t c /haproxy / haproxy . c f g with secondnode
on 443 por t do
- exp ect ( chef_run ) . to r e n d e r _ f i l e ( / e t c / haproxy / haproxy . c f g
) . with_content (/#{Regexp . quote ( s e r v e r t e s t 2 . org
1 9 2 . 1 6 8 . 1 . 2 : 4 4 3 weig ht 1 maxconn 1024 check ) }/)
- end
-
- end
80 end
-
- end
And we can check our tests:
Line 1 $ rsp e c s pec / u n i t / r e c i p e s / haproxy_spec . rb
- . . . . . . . . .
-
- F i n ished i n 3 3.34 sec ond s
5 9 examples , 0 f a i l u r e s
As a result, haproxy recipe fully covered by Chefspec and Chef Zero.
Using with Test Kitchen
To use Chef Zero with test kitchen, you should change provisioner type in
.kitchen.yml:
Line 1
- d r i v e r :
- name : vagrant
-
5 p r o v i s i o n e r :
- name : c hef _ze ro
- rol es_ pat h : " t e s t / chefz e r o / r o l e s "
- environments_path : " t e s t / che f z e ro / environments "
- nodes_path : " t e s t / ch efz e r o / nodes "
10 c l i e n t _ r b :
- environment : t e s t
-
- platform s :
- name : ubuntu 12.04
15 name : ubuntu 10.04
- name : cen to s 6.4
-
- s u i t e s :
- name : d e f a u l t
20 r u n _ l i s t :
- r e c i p e [ my_cool_app : : d e f a u l t ]
- a t t r i b u t e s :
128
6.5. Chef Zero
- name : node
- r u n _ l i s t :
25 r e c i p e [ my_cool_app : : d e f a u l t ]
- r e c i p e [ my_cool_app : : node ]
- a t t r i b u t e s :
- name : haproxy
- r u n _ l i s t :
30 r e c i p e [ my_cool_app : : haproxy ]
- a t t r i b u t e s :
- e x c l u d e s :
- c entos 6.4
As you can see, we changed provisioner name to chef_zero. Also we set
roles_path, environments_path and nodes_path. This folders will used to upload
to Chef Zero test data - roles, environments and nodes. And we set client_rb
attribute, which allow add attributes for chef client. Here we set environment
as «test».
Next we create new test suite, called «haproxy». Let’s check it:
Line 1 $ k i t c h e n l i s t
- I n s t a n c e Dri v er P r o v i s i o n e r Last Action
- d e f a u l t ubuntu 1204 Vagrant ChefZero <Not Created>
- d e f a u l t ubuntu 1004 Vagrant ChefZero <Not Created>
5 d e f a u l t c entos 64 Vagrant ChefZero <Not Created>
- nodeubuntu 1204 Vagrant ChefZero <Not Created>
- nodeubuntu 1004 Vagrant ChefZero <Not Created>
- nodece ntos 64 Vagrant ChefZero <Not Created>
- haproxyubuntu 1204 Vagrant ChefZero <Not Created>
10 haproxyubuntu 1004 Vagrant ChefZero <Not Created>
Now lets create folder «integration/haproxy/serverspec» and add to it tests
for haproxy recipe:
Line 1 r e q u i r e s e r v e r s p e c
-
- i n c l u d e S e r v e r s p e c : : He lp er : : Exec
- i n c l u d e S e r v e r s p e c : : He lp er : : DetectOS
5
- RSpec . c o n f i g u r e do | c |
- c . b e f o r e : a l l do
- c . path = / s bin : / usr / s b i n
- end
10 end
-
- d e s c r i b e " Haproxy Daemon" do
-
- d e s c r i b e package ( haproxy ) do
15 i t { sho ul d b e _ i n s t a l l e d }
- end
-
- d e s c r i b e s e r v i c e ( haproxy ) do
- i t { sho ul d be_enabled }
20 i t { sho ul d be_running }
- end
129
6.5. Chef Zero
-
- [8 0 , 4 4 3 ] . each do | por t |
- i t " i s l i s t e n i n g on p ort #{p ort } " do
25 exp ect ( p ort ( p ort ) ) . to b e _ l i s t e n i n g
- end
- end
-
- d e s c r i b e f i l e ( / e t c / haproxy / haproxy . c f g ) do
30 i t { sho ul d b e _ f i l e }
- i t s ( : c o nte nt ) { shoul d match /#{Regexp . quote ( s e r v e r l e o p ard .
in . ua 1 2 7 . 0 . 0 . 1 : 8 0 weight 1 maxconn 1024 check ) }/ }
- i t s ( : c o nte nt ) { shoul d match /#{Regexp . quote ( s e r v e r l e o p ard .
in . ua 1 2 7 . 0 . 0 . 1 : 4 4 3 weight 1 maxconn 1024 check ) }/ }
- end
-
35 end
Almost ready. As you can see, we should generate by tests haproxy config
with one node (hostname «leopard.in.ua» and ip address «127.0.0.1»). We
should prepare this node for tests. In folder «test/chef-zero/nodes» we create
node:
Line 1 {
- " name " : " f i r s t node " ,
- " j s o n _ c l a s s " : " Chef : : Node " ,
- " chef_type " : " node " ,
5 " chef_environment " : " t e s t " ,
- " normal " : {
- " fqdn " : " 1 2 7 . 0 . 0 . 1 " ,
- " hostname " : " l eopar d . i n . ua " ,
- " i p a d d r e s s " : " 1 2 7 . 0 . 0 . 1 "
10 } ,
- " r u n _ l i s t " : [ " r o l e [ web ] " ]
- }
It should have «test» environment and use «web» role, because only by
this conditions we will select this node for haproxy config. Also we set «host-
name» and «ipaddress», because this is not real node and Ohai will not fill this
attributes. After this we should create «test» environment and «web» role:
Line 1 {
- " name " : " t e s t " ,
- " d e s c r i p t i o n " : " t e s t environment " ,
- " chef_type " : " environment " ,
5 " j s o n _ c l a s s " : " Chef : : Environment " ,
- " d e f a u l t _ a t t r i b u t e s " : {}
- }
Line 1 {
- " name " : " web " ,
- " d e s c r i p t i o n " : " The web r o l e " ,
- " chef_type " : " r o l e " ,
5 " j s o n _ c l a s s " : " Chef : : Role " ,
- " d e f a u l t _ a t t r i b u t e s " : {
130
6.6. Minitest
- } ,
- " r u n _ l i s t " : [ ]
- }
All this data will load automatically into Chef Zero by Test Kitchen. And
now we cat test our haproxy recipe by Test Kitchen:
Line 1 $ k i t c h e n t e s t haproxyubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > C le aning up any p r i o r i n s t a n c e s o f <haproxyubuntu 1204>
- > Des tro yin g <haproxyubuntu 1204 >...
5 . . .
- Uploading /tmp/ buss e r / s u i t e s / s e r v e r s p e c / haproxy_spec . rb ( mode
=0644)
- > Running s e r v e r s p e c t e s t s u i t e
- / opt / c h e f /embedded/ bin / ruby I /tmp/ buss e r / s u i t e s / s e r v e r s p e c S /
opt / c h e f /embedded/ bin / rspec /tmp/ buss e r / s u i t e s / s e r v e r s p e c /
haproxy_spec . rb c o l o r format documentation
-
10 Haproxy Daemon
- i s l i s t e n i n g on p ort 80
- i s l i s t e n i n g on p ort 443
- Package " haproxy "
- sh ould be i n s t a l l e d
15 Se r v i c e " haproxy "
- sh ould be e na bled
- sh ould be running
- F i l e " / e t c / haproxy / haproxy . c f g "
- sh ould be f i l e
20 con tent
- s hould match / s e r v e r \ l eopar d \ . i n \ . ua\ 1 2 7 \ . 0 \ . 0 \ . 1 : 8 0 \
weight \ 1\ maxconn\ 1024\ check /
- con tent
- s hould match / s e r v e r \ l eopar d \ . i n \ . ua\ 1 2 7 \ . 0 \ . 0 \ . 1 : 4 4 3 \
weight \ 1\ maxconn\ 1024\ check /
-
25 F i n ished i n 0.55 591 sec onds
- 8 examples , 0 f a i l u r e s
- F inish e d v e r i f y i n g <haproxyubuntu1204> (0m2.20 s ) .
- . . .
- F inish e d t e s t i n g <haproxyubuntu 1204> (3m26. 6 9 s ) .
30 > Kitchen i s f i n i s h e d . (3m28 . 0 5 s )
As a result, haproxy recipe fully covered by Test Kitchen, Serverspec and
Chef Zero.
6.6 Minitest
Minitest provides a complete suite of testing facilities supporting TDD,
BDD, mocking, and benchmarking. Minitest doesn’t reinvent anything that
ruby already provides, like: classes, modules, inheritance, methods. This
131
6.6. Minitest
means you only have to learn ruby to use minitest and all of your regular
OO practices like extract-method refactorings still apply.
Exists two ways of minitest usage:
With Test Kitchen (like bats or serverspec)
Testing node after cooking by minitest-chef-handler
Second way is very interesting, because allows you to check tests even on
production environment. This allows to be sure, that tests pass on check run
of chef client on node.
Let’s consider each example.
Test Kitchen
As you remember, we used bats and serverspec with Test Kitchen. But
inside it you can use any test framework, for which exists «busser». Right now
you can find:
busser-minitest
busser-bats
busser-bash
busser-serverspec
busser-rspec
busser-cucumber
Let’s add little test inside our cookbook:
Line 1 r e q u i r e m i n i t e s t / autorun
-
- d e s c r i b e " my_cool_app : : d e f a u l t " do
-
5 i t " has c r e a t e d / var /www/my_cool_app/ ind ex . html " do
- a s s e r t F i l e . e x i s t s ?( " / var /www/my_cool_app/ index . html " )
- end
-
- end
And check how it works:
Line 1 $ k i t c h e n t e s t d e f a u l t ubuntu 1204
- > S t a r t i n g Kitchen ( v1 . 2 . 1 )
- > C le aning up any p r i o r i n s t a n c e s o f <d e f a u l t ubuntu1204>
- > Des tro yin g <d e f a u l t ubuntu 1204 >...
5 . . .
- Pl ugin m i n i t e s t i n s t a l l e d ( v e r s i o n 0 . 2 . 0 )
- > Running p o s t i n s t a l l f o r m i n i t e s t p l u g i n
- . . .
- > Running m i n i t e s t t e s t s u i t e
10 / opt / c h e f /embedded/ bin / ruby I " / opt / ch e f /embedded/ l i b / ruby / 1 . 9 . 1 "
" / opt / che f /embedded/ l i b / ruby / 1 . 9 . 1 / rake / rak e_t est _ loa der . rb " "
/tmp/ busser / s u i t e s / m i n i t e s t / t e s t _ d e f a u l t . rb "
132
6.6. Minitest
- Run o p t i o n s : s eed 59554
-
- # Running t e s t s :
-
15 .
-
- F i n ished t e s t s i n 0 .00 2407 s , 415 .534 7 t e s t s / s , 415 .53 47 a s s e r t i o n s
/ s .
-
- 1 t e s t s , 1 a s s e r t i o n s , 0 f a i l u r e s , 0 e r r o r s , 0 s k i p s
20 F inish e d v e r i f y i n g <d e f a u l t ubuntu 1204> (0m2. 3 9 s ) .
- > Kitchen i s f i n i s h e d . (1m27 . 9 7 s )
As you can see, minitest doesn’t have additional helpers, like have server-
spec (be_listening, be_running, etc). What is why let’s consider second way of
using it.
Minitest Chef Handler
Minitest-chef-handler run minitest suites after your Chef recipes to check
the status of your system. It can be very useful, because you testing inside
your real environment. Exists 2 option to use it:
Option 1: Add the report handler to your client.rb or solo.rb file:
Line 1 r e q u i r e m i n itest chefh andl er
-
- r e port _ h and l e rs << MiniTest : : Chef : : Handler . new
Option 2: Using minitest-handler cookbook, which should be added in the
end of run_list:
Line 1 c h e f . r u n _ l i s t = [
- " your r e c i p e s " ,
- " m i n i t e st h a ndl er "
- ]
I prefer second variant. Just add in Berksfile cookbook minitesthandler:
Line 1 s ource " http : / / a pi . b e r k s h e l f . com "
-
- metadata
-
5 cookbook " m i n i test h and l er "
Add it in run_list:
Line 1 s u i t e s :
- name : d e f a u l t
- r u n _ l i s t :
- r e c i p e [ my_cool_app : : d e f a u l t ]
5 r e c i p e [ minites t h a ndl er ]
Next we should write some tests. Let’s create folder to its:
Line 1 $ mkdir p f i l e s / d e f a u l t / t e s t
133
6.7. Cucumber
And add some tests to cover default recipe:
Download my-server-cloud/site- . . .
Line 1 r e q u i r e m i n i t e s t / s pec
-
- d e s c r i b e _ r e c i p e my_cool_app : : d e f a u l t do
-
5 i t " i n s t a l l ntp package " do
- package ( ntp ) . must_be_installed
- end
-
- i t " i n s t a l l g i t package " do
10 i f " ubuntu " == node [ plat form ] && node [ p lat f orm_ v ersi o n ] .
to_f <= 1 0 .04
- pack = " g i t c o r e "
- e l s e
- pack = " g i t "
- end
15 package ( pack ) . must _be_insta lled
- end
-
- i t " nginx must running " do
- s e r v i c e ( " nginx " ) . must_be_running
20 end
-
- end
Finally run our chef-client by Test Kitchen «converge» command:
Line 1 $ k i t c h e n conve rge d e f a u l t ubuntu 1204
- . . .
- Running h a n d l e r s :
- [2014 0327T20 : 3 0 : 0 3 + 0 0 : 0 0 ] INFO : Running r e p o r t h a n d l e r s
5 Run o p t i o n s : v see d 42106
-
- # Running t e s t s :
-
- r e c i p e : : my_cool_app : : d e f a u l t#t e s t _ 0 0 0 2 _ i n s t a l l g i t package = 0.14
s = .
10 r e c i p e : : my_cool_app : : d e f a u l t#t e s t _ 0 0 0 1 _ i n s t a l l ntp package = 0 . 08
s = .
- r e c i p e : : my_cool_app : : d e f a u l t#test_0003_nginx must running = 0 . 0 9 s
= .
As a result, after launching all recipes in run_list, last recipe minitesthandler
::default run minitest to check status of the node.
6.7 Cucumber
Cucumber is a tool for running automated tests written in plain language.
Because they’re written in plain language, they can be read by anyone on your
134
6.7. Cucumber
team. Because they can be read by anyone, you can use them to help improve
communication, collaboration and trust on your team.
Example
Therefore we need only add the following three gems to a Gemfile:
Line 1 gem cucumber
- gem r spec e x p e c t a t i o n s
- gem l e i b n i z
And you should to execute bundle command to install this gem.
Next we should create directory for our tests:
Line 1 $ mkdir p f e a t u r e s / s t e p _ d e f i n i t i o n s
- $ mkdir p f e a t u r e s / s t e p _ d e f i n i t i o n s / su pport
And require needed libs in features/step_definitions/support/env.rb file:
Line 1 r e q u i r e l e i b n i z ’
- r e q u i r e fa raday
Now we should create file, which will contain tests definitions. Let’s call it
features/working_web_page.feature:
Line 1 Featur e : Customer can use my c o o l web app
-
- In o r der t o get more payment c us to me rs
- As a b u s i n e s s owner
5 I want web u s e r s to be able use my c o o l web app
-
- Background :
- Given I have p r o v i s i o n e d the f o l l o w i n g i n f r a s t r u c t u r e :
- | S erve r Name | Operating System | Ver sion | Chef V ersio n |
Run L i s t |
10 | l o c a l h o s t | ubuntu | 1 2 . 04 | 1 1 . 4 . 4 |
my_cool_app : : d e f a u l t |
-
- And I have run Chef
-
- S c e n a r i o : User v i s i t s home page
15 Given a u r l " http : / / example . org "
- When a web user browses to the URL
- Then t he u s e r s hould s e e " This i s my co o l web app "
- And clean up t e s t env
Next, we can try run cucumber:
Line 1 $ cucumber
- Feature : Customer can use my c o o l web app
-
- In o r der t o get more payment c us to me rs
5 As a b u s i n e s s owner
- I want web u s e r s to be able use my c o o l web app
-
135
6.7. Cucumber
- Background : #
f e a t u r e s /working_web_page . f e a t u r e : 7
- Given I have p r o v i s i o n e d the f o l l o w i n g i n f r a s t r u c t u r e : #
f e a t u r e s /working_web_page . f e a t u r e : 8
10 | S e rver Name | Operating System | Ver sion | Chef V ersio n |
Run L i s t |
- | l o c a l h o s t | ubuntu | 1 2 .04 | 1 1 . 4 . 4 |
my_cool_app : : d e f a u l t |
- And I have run Chef #
f e a t u r e s /working_web_page . f e a t u r e : 1 2
-
- S c e n a r i o : User v i s i t s home page #
f e a t u r e s /working_web_page . f e a t u r e : 1 4
15 Given a u r l http : / / example . org #
f e a t u r e s /working_web_page . f e a t u r e : 1 5
- When a web user browses to the URL #
f e a t u r e s /working_web_page . f e a t u r e : 1 6
- Then t he u s e r s hould s e e " This i s my co o l web app " #
f e a t u r e s /working_web_page . f e a t u r e : 1 7
-
- 1 s c e n a r i o (1 u ndef ine d )
20 5 s t e p s (5 u n def ined )
- 0m0. 012 s
-
- You can implement step d e f i n i t i o n s f o r u nde f ine d s t e p s with t h e s e
s n i p p e t s :
-
25 Given (/^ I have p r o v i s i o n e d the f o l l o w i n g i n f r a s t r u c t u r e : $ /) do |
t a b l e |
- # t a b l e i s a Cucumber : : Ast : : Table
- pending # e x p r e s s the regexp above with the code you wish you
had
- end
-
30 Given (/^ I have run Chef$ /) do
- pending # e x p r e s s the regexp above with the code you wish you
had
- end
-
- Given (/^ a u r l http : \ / \/ example \ . org$ /) do
35 pending # e x p r e s s the regexp above with the code you wish you
had
- end
-
- When(/^ a web u s er browses to the URL$/) do
- pending # e x p r e s s the regexp above with the code you wish you
had
40 end
-
- Then (/^ the u s e r s ho uld s e e " ( . * ? ) " $ /) do | arg1 |
- pending # e x p r e s s the regexp above with the code you wish you
had
- end
136
6.7. Cucumber
45
- I f you want s n i p p e t s i n a d i f f e r e n t programming language ,
- j u s t make sur e a f i l e with the a p p r o p r i a t e f i l e e x t e n s i o n
- e x i s t s where cucumber l o o k s f o r s t e p d e f i n i t i o n s .
As you can see, we dont have any line of real tests. Let’s add tests in file
features/step_definitions/working_web_page.rb:
Line 1 Given (/^ I have p r o v i s i o n e d the f o l l o w i n g i n f r a s t r u c t u r e : $ /) do |
s p e c i f i c a t i o n |
- @i n f r a s t r u c t u r e = L e i b n i z . b u i l d ( s p e c i f i c a t i o n )
- end
-
5 Given (/^ I have run Chef$ /) do
- @i n f r a s t r u c t u r e . d e stroy
- @i n f r a s t r u c t u r e . c onver ge
- end
-
10 Given (/^ a u r l " ( . * ? ) " $ /) do | u r l |
- @host_header = u r l . s p l i t ( / ) . l a s t
- end
-
- When(/^ a web u s er browses to the URL$/) do
15 c onnec t i on = Faraday . new ( : u r l => " http ://#{ @ i n f r a s t r u c t u r e [
l o c a l h o s t ] . i p } " , : h eade rs => { Host => @host_header }) do |
far aday |
- far aday . ada pt er Faraday . d e faul t _ad a pter
- end
- @page = c o n necti o n . g e t ( / ) . body
- end
20
- Then (/^ the u s e r s ho uld s e e " ( . * ? ) " $ /) do | con t ent |
- exp ect ( @page ) . to match /#{ con ten t }/
- end
-
25 Then (/^ cl eanup t e s t env$ /) do
- @i n f r a s t r u c t u r e . d e stroy i f @ i n f r a s t r u c t u r e
- end
Leibniz gem read infrastructure configuration from our specs inside «Back-
ground» and use Test Kitchen to create it. Next, in «I have run Chef» we
cleanup old and create new node, install chef client and run it inside node.
After this we using Faraday gem to do HTTP request inside node and get root
page content. We are checking, what this content should contain «This is my
cool web app». In this case we will be sure, what nginx installed, running and
serve our web page. In the end we added «cleanup test en, which would
remove node after tests.
Finally, we cat test our cucumber tests:
Line 1 $ cucumber
- Feature : Customer can use my c o o l web app
-
- In o r der t o get more payment c us to me rs
137
6.8. Static Analysis and Linting Tools
5 As a b u s i n e s s owner
- I want web u s e r s to be able use my c o o l web app
-
- Background : #
f e a t u r e s /working_web_page . f e a t u r e : 7
- Given I have p r o v i s i o n e d the f o l l o w i n g i n f r a s t r u c t u r e : #
f e a t u r e s / s t e p _ d e f i n i t i o n s /working_web_page . rb : 1
10 | S e rver Name | Operating System | Ver sion | Chef V ersio n |
Run L i s t |
- | l o c a l h o s t | ubuntu | 1 2 .04 | 1 1 . 4 . 4 |
my_cool_app : : d e f a u l t |
- > Des tro yin g <l e i b n i z l o c a l h o s t > . . .
- ==> d e f a u l t : For cing shutdown o f VM. . .
- ==> d e f a u l t : D est roy ing VM and a s s o c i a t e d d r i v e s . . .
15 . . .
-
- Chef C l i e n t f i n i s h e d , 28 r e s o u r c e s updated
- F inish e d conv e rgin g <l e i b n i z l o c a l h o s t > (1m6. 9 8 s ) .
- And I have run Chef #
f e a t u r e s / s t e p _ d e f i n i t i o n s /working_web_page . rb : 5
20
- S c e n a r i o : User v i s i t s home page #
f e a t u r e s /working_web_page . f e a t u r e : 1 4
- Given a u r l " http : / / example . org " #
f e a t u r e s / s t e p _ d e f i n i t i o n s /working_web_page . rb : 1 0
- When a web user browses to the URL #
f e a t u r e s / s t e p _ d e f i n i t i o n s /working_web_page . rb : 1 4
- Then t he u s e r s hould s e e " This i s my co o l web app " #
f e a t u r e s / s t e p _ d e f i n i t i o n s /working_web_page . rb : 2 1
25 > Des tro yin g <l e i b n i z l o c a l h o s t > . . .
- ==> d e f a u l t : For cing shutdown o f VM. . .
- ==> d e f a u l t : D est roy ing VM and a s s o c i a t e d d r i v e s . . .
- Vagrant i n s t a n c e <l e i b n i z l o c a l h o s t > dest r o yed .
- F inish e d d e s t r o y i n g <l e i b n i z l o c a l h o s t > (0m7. 1 5 s ) .
30 And clean up t e s t env #
f e a t u r e s / s t e p _ d e f i n i t i o n s /working_web_page . rb : 2 5
-
- 1 s c e n a r i o (1 pas se d )
- 6 s t e p s (6 p as sed )
- 2m41. 0 0 4 s
As a result, we cover our default recipe by using cucumber.
6.8 Static Analysis and Linting Tools
Foodcritic
Foodcritic is a lint tool for your Opscode Chef cookbooks. 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
138
6.8. Static Analysis and Linting Tools
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 subjec-
tive 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.
On main site you can find list of rules. Also you can define own list of
rules (if you need this). Foodcritic is like jslint for cookbooks. At the bare
minimum, you should run foodcritic against all your cookbooks.
Example
We need to add foodcritic gems in Gemfile inside our my_cool_app cook-
book:
Line 1 s ource h ttps : / / rubygems . org
-
- gem b e r k s h e l f
- gem f o o d c r i t i c
And you should to execute bundle command to install this gem. Next we
can to check my_cool_app cookbook by foodcritic:
Line 1 $ f o o d c r i t i c .
- FC015 : Consi de r c o n v e r t i n g d e f i n i t i o n t o a LWRP: . / d e f i n i t i o n s /
enable_web_site . rb : 1
- FC021 : Resource c o n d i t i o n i n p r o v i d e r may not behave as exp ect ed :
. / p r o v i d e r s /know_host . rb : 3 9
- FC048 : P r e f e r M ixlib : : S hellOut : . / l i b r a r i e s / provider_known_host . rb
: 5 6
5 FC048 : P r e f e r M ixlib : : S hellOut : . / p r o v i d e r s /know_host . rb : 3 3
We have a few warnings in the code. Let’s fix them:
Line 1 my_cool_app/ l i b r a r i e s / provider_known_host . rb
- @@ 53,7 +53 ,8 @@ c l a s s Chef
- p r i v a t e
-
5 d e f i n s u r e _ f o r _ f i l e ( new_resource )
- key = ( new_resource . key | | sshkeyscan H p #{
new_resource . por t } #{new_resource . hos t } 2>&1)
- + cmd = Mix lib : : Sh ellOut . new ( " sshkeyscan H p #{
new_resource . por t } #{new_resource . hos t } 2>&1" )
- + key = ( new_resource . key | | cmd . run_command . stdo ut )
- comment = key . s p l i t ( " \n " ) . f i r s t | | " "
10
- my_cool_app/ p r o v i d e r s /know_host . rb
- @@ 30,13 +30 ,15 @@ a c t i o n : d e l e t e do
- end
-
15 def i n s u r e _ f o r _ f i l e ( new_resource )
139
6.8. Static Analysis and Linting Tools
- key = ( new_resource . key | | sshkeyscan H p #{ new_resource .
por t } #{new_resource . hos t } 2>&1)
- + cmd = Mixli b : : ShellO ut . new ( " sshkeyscan H p #{new_resource .
por t } #{new_resource . hos t } 2>&1" )
- + key = ( new_resource . key | | cmd . run_command . stdo ut )
- comment = key . s p l i t ( " \n " ) . f i r s t | | " "
20
- Chef : : A p p l i c a t i o n . f a t a l ! " Could not r e s o l v e #{new_resource . hos t
} " i f key =~ / g e t a d d r i n f o /
-
- # Ensure tha t the f i l e e x i s t s and has minimal c ont ent ( r e q u i r e d
by Chef : : U t i l : : Fi l e E d i t )
- f i l e new_resource . known_hosts_file do
25 + f i l e " Check what f i l e #{new_resource . known_hosts_file } e x i s t s
f o r #{new_resource . name} " do
- + path new_resource . known_hosts_file
- a c t i o n : c r e a t e
- backup f a l s e
- co nte nt # This f i l e must c o n t ain at l e a s t one l i n e .
This i s t hat l i n e .
And run again foodcritic :
Line 1 $ f o o d c r i t i c .
- FC015 : Consi de r c o n v e r t i n g d e f i n i t i o n t o a LWRP: . / d e f i n i t i o n s /
enable_web_site . rb : 1
As you can see almost all warnings fixed.
Integration by Rake
Foodcritic has unreleased experimental support for Rake included. With
foodcritic and rake in your Gemfile your Rakefile would look like this:
Line 1 r e q u i r e f o o d c r i t i c
- t a sk : d e f a u l t => [ : f o o d c r i t i c ]
- F o odC r itic : : Rake : : LintTask . new
You can also pass a block when instantiating to configure the lint options:
Line 1 r e q u i r e f o o d c r i t i c
- t a sk : d e f a u l t => [ : f o o d c r i t i c ]
- F o odC r itic : : Rake : : LintTask . new do | t |
- t . o p t i o n s = { : f a i l _ t a g s => [ c o r r e c t n e s s ] }
5 end
Integration by Thor
While Rake is the old grand-daddy of Ruby build tools, a number of people
prefer to use Thor.
We need to add thor-foodcritic gem in Gemfile inside our my_cool_app cook-
book:
140
6.8. Static Analysis and Linting Tools
Line 1 s ource h ttps : / / rubygems . org
-
- gem b e r k s h e l f
- gem f o o d c r i t i c
5 gem thor f o o d c r i t i c
And you should to execute bundle command to install this gem. Add the
FoodCritic tasks to Thorfile:
Line 1 # enc od ing : utf 8
-
- r e q u i r e bundl er
- r e q u i r e bundl er / s etup
5 r e q u i r e b e r k s h e l f / tho r
- r e q u i r e t hor / f o o d c r i t i c
And then get a list of your thor tasks:
Line 1 $ tho r l i s t
- . . .
- f o o d c r i t i c
-
5 t hor f o o d c r i t i c : l i n t # Run a l i n t t e s t a g a i n s t the s p e c i f i e d
Cookbook and Role paths or o t h e r w i s e your c u r r e n t working
d i r e c t o r y .
-
- . . .
Run the lint task to get a review:
Line 1 $ tho r f o o d c r i t i c : l i n t
- FC015 : Consi de r c o n v e r t i n g d e f i n i t i o n t o a LWRP: / Us er s / l e o /
Documents/chef_book / code /mys e r v er cloud / s i t e cookbooks /
my_cool_app/ d e f i n i t i o n s / enable_web_site . rb : 1
Get info about options:
Line 1 $ tho r hel p f o o d c r i t i c : l i n t
- Usage :
- tho r f o o d c r i t i c : l i n t
-
5 Options :
- B, [−− cookbookpath=one two t h r e e ] # Cookbook path ( s ) to check
.
- # D efau l t : / Users / l e o /
Documents/chef_book / code
/mys e r v er cloud / s i t e
cookbooks /my_cool_app
- R, [−− rol e path=one two t h r e e ] # Role path ( s ) to check .
- t , [−− t a g s=one two t h r e e ] # Only check a g a i n s t r u l e s
with the s p e c i f i e d t ags .
10 I , [−− i n c l u d e=one two t h r e e ] # A d d itional r u l e f i l e path
( s ) to l oad .
- f , [−− epi c f a i l=one two t h r e e ] # F a i l the b u i l d i f any o f
the s p e c i f i e d t ags a re matched .
- e , [ exclude p aths=one two t h r e e ] # Paths to excl u de when
running t e s t s .
141
6.8. Static Analysis and Linting Tools
- # D efau l t : [ " t e s t / * * /* " , "
spe c / * * / * " , " f e a t u r e s
/ * * / * " ]
-
15 Run a l i n t t e s t a g a i n s t the s p e c i f i e d Cookbook and Role paths o r
o t h e r w i s e your c u r r e n t working d i r e c t o r y .
We can ignore some rules by using tags. For example, to ignore FC015
warning, we can run command:
Line 1 $ tho r f o o d c r i t i c : l i n t t ~FC015
-
- $
Or we can use . foodcritic file inside our cookbook folder and add tags inside
it:
Line 1 $ c a t . f o o d c r i t i c
- ~FC015
-
- $ tho r f o o d c r i t i c : l i n t
5
- $
Here we use the tilde ~ to exclude FC015. For example, if we need check
only rules, which have tags services and style , we use directly by foodcritic
command:
Line 1 $ f o o d c r i t i c t s t y l e , s e r v i c e s .
-
- $
Or with Thor:
Line 1 $ tho r f o o d c r i t i c : l i n t t s t y l e , s e r v i c e s
-
- $
As you can see, in this case you can add this command in your Continuous
integration (CI) and check your cookbook by foodcritic.
Extra Rules
Except standart rules exists additional rules that you can use with food-
critic:
CustomInk Foodcritic Rules
Etsy Foodcritic Rules
Lookout Foodcritic Rules
And, of course, you can write own number of rules for your cookbooks.
RuboCop
RuboCop is a Ruby static code analyzer. Out of the box it will enforce
many of the guidelines outlined in the community Ruby Style Guide.
142
6.8. Static Analysis and Linting Tools
Example
First we should add this gem in Gemfile:
Line 1 s ource h ttps : / / rubygems . org
-
- gem rubocop , r e q u i r e : f a l s e
And you should to execute bundle command to install this gems.
After this you can use command line tool «rubocop» to check your Ruby
code by community style guide:
Line 1 $ rubocop
- I n s p e c t i n g 24 f i l e s
- CCCCWCCCCCCCCCCCCCCCWWCC
-
5 O f f e n s e s :
-
- a t t r i b u t e s / d e f a u l t . rb : 4 : 5 5 : C: Source f i l e s should end with a
new line (\n) .
- d e f a u l t [ my_cool_app ] [ web_host ] = example . org
- ^
10
- . . .
-
- t e s t / i n t e g r a t i o n / node/ s e r v e r s p e c / nginx_daemon_spec . rb : 1 8 : 6 : C:
P r e f e r s i n g l e quoted s t r i n g s when you don t need s t r i n g
i n t e r p o l a t i o n or s p e c i a l symbols .
- i t " has a running s e r v i c e o f nginx " do
15 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- t e s t / i n t e g r a t i o n / node/ s e r v e r s p e c / nginx_daemon_spec . rb : 1 9 : 2 0 : C:
P r e f e r s i n g l e quoted s t r i n g s when you don t need s t r i n g
i n t e r p o l a t i o n or s p e c i a l symbols .
- exp ect ( s e r v i c e ( " nginx " ) ) . t o be_running
- ^^^^^^^
- t e s t / i n t e g r a t i o n / node/ s e r v e r s p e c / nginx_daemon_spec . rb : 2 2 : 3 : C:
Source f i l e s s hould end with a new lin e (\n) .
20 end
- ^
- t e s t / i n t e g r a t i o n / node/ s e r v e r s p e c / node_spec . rb : 1 2 : 1 0 : C: P r e f e r
s i n g l e quoted s t r i n g s when you don t need s t r i n g i n t e r p o l a t i o n
or s p e c i a l symbols .
- d e s c r i b e " Node " do
- ^^^^^^
25 t e s t / i n t e g r a t i o n / node/ s e r v e r s p e c / node_spec . rb : 1 8 : 3 : C: Sour ce
f i l e s shou ld end with a new l ine (\n) .
- end
- ^
- 24 f i l e s i n s pecte d , 229 o f f e n s e s d e t e c t e d
As you can see, this code contain many problems in Ruby style, which can
be fixed.
The behavior of RuboCop can be controlled via the .rubocop.yml configu-
ration file. It makes it possible to enable/disable certain cops (checks) and
143
6.8. Static Analysis and Linting Tools
to alter their behavior if they accept any parameters. The file can be placed
either in your home directory or in some project directory.
Strainer
Strainer is a gem for isolating and testing individual chef cookbooks. It
allows you to keep all your cookbooks in a single repository (saving you time
and money), while having all the benefits of individual repositories. But also
you can use Strainer in «standalone» mode. This allows you to use Strainer
file from within a cookbook.
Example
First, add it in Gemfile:
Line 1 s ource h ttps : / / rubygems . org
-
- gem b e r k s h e l f
- gem f o o d c r i t i c
5 gem thor f o o d c r i t i c
- gem c h e f s p e c
- gem t e s t k i t c h e n
- gem c he f z e r o
-
10 group : i n t e g r a t i o n do
- gem k itc hen vagrant
- gem cucumber
- gem r spece x p e c t a t i o n s
- gem l e i b n i z , ~> 0 . 2 . 1
15 end
-
- gem rubocop , r e q u i r e : f a l s e
- gem s t r a i n e r , r e q u i r e : f a l s e
And run «bundle» command to install it.
Next you must create file Strainerfile with such content:
Line 1 c h e f s p e c : bundle exec r s p e c c o l o r
- kitchen : bundle exec th or k i t c h en : d e f a u l t ubuntu 1204
Strainer have similar functionality as Foreman, but for running tests inside
cookbook(s). Now we cat test our cookbook:
Line 1 $ bundle exec s t r a i n e r t e s t
- # S t r a i n i n g my_cool_app ( v0 . 1 . 0 )
- c h e f s p e c | bundle exec r s p e c c o l o r
- c h e f s p e c | . . . . . . . . . . . . . . . . . .
5 c h e f s p e c | F i nish e d i n 1 minute 2 2 .43 sec ond s
- c h e f s p e c | 23 examples , 0 f a i l u r e s
- c h e f s p e c | SUCCESS!
- kitchen | bundle exec t hor k i t c h e n : d e f a u l t ubuntu
1204
144
6.9. Summary
- kitchen | > C le aning up any p r i o r i n s t a n c e s o f <
d e f a u l t ubuntu 1204>
10 kitchen | > De str oyin g <d e f a u l t ubuntu 1204 >...
-
- . . .
-
- kitchen | Vagrant i n s t a n c e <d e f a u l t ubuntu
1204> de s troy e d .
15 kitchen | F i n ished d e s t r o y i n g <d e f a u l t ubuntu
1204> (0m6. 8 7 s ) .
- kitchen |
- kitchen | SUCCESS!
- S t r a i n e r marked b u i l d OK
Also you can use it with Rake or Thor command line tools.
6.9 Summary
Chef cookbook testing is very important part of cookbook development.
Testing process allows developers and devops to be sure, that important parts
of cookbook work as expected and new updates and features will not break
cookbook workflow.
145
7
Tips and Tricks
7.1 Wrapper cookbook
A wrapper cookbook wraps an upstream cookbook to change its behavior
without forking it.
There are two main reasons you might want to do this:
Codifying the standard settings for your organization or business unit’s
use of that cookbook without placing those attributes in a role
Modifying the behavior of an upstream cookbook
Codifying Standards in Your Organization
Suppose I use the community ntp cookbook but I want to enforce a set
of timeservers across my infrastructure. Instead of running this cookbook
directly, I could create an acmecontp cookbook with the following settings:
Code 7.1 acmeco-ntp/attributes/default.rb
Line 1 d e f a u l t [ ntp ] [ p e e r s ] = [ ntp1 . acmeco . com , ntp2 . acmeco . com ]
Code 7.2 acmeco-ntp/recipes/default.rb
Line 1 i n c l u d e _ r e c i p e ntp
Now I can simply run recipe [acmecontp] in my infrastructure and the default
settings will take effect.
Note that it is not necessary to use normal or override priority here. De-
pendent cookbooks are loaded first by Chef Client and their attribute files are
evaluated before those of the caller.
Modifying Upstream Cookbook Behavior
Sometimes you want to modify the behavior of an upstream cookbook with-
out forking it. For example, let’s take the PostgreSQL community cookbook.
146
7.1. Wrapper cookbook
It installs whatever PostgreSQL packages come from your operating system
distribution. Suppose you want to install version 9.4 of PostgreSQL on an
operating system that would not natively provide it (e.g. RedHat Enterprise
Linux 6) but those packages can be found in the official PostgreSQL Global
Development Group (PGDG) repository. How would you go about doing that?
You could write a wrapper cookbook that set the right attributes:
Code 7.3 acmeco-postgresql/attributes/default.rb
Line 1 d e f a u l t [ p o s t g r e s q l ] [ v e r s i o n ] = 9 . 4
- d e f a u l t [ p o s t g r e s q l ] [ c l i e n t ] [ packages ] = [ " p o s t g r e s q l #{node [
p o s t g r e s q l ] [ v e r s i o n ] . s p l i t ( . ) . j o i n } devel " ]
- d e f a u l t [ p o s t g r e s q l ] [ s e r v e r ] [ packages ] = [ " p o s t g r e s q l#{node [
p o s t g r e s q l ] [ v e r s i o n ] . s p l i t ( . ) . j o i n } s e r v e r " ]
- d e f a u l t [ p o s t g r e s q l ] [ c o n t r i b ] [ packages ] = [ " p o s t g r e s q l#{node
[ p o s t g r e s q l ’ ] [ v e r s i o n ] . s p l i t ( . ) . j o i n } co n t r i b " ]
5 d e f a u l t [ p o s t g r e s q l ] [ d i r ] = " / var / l i b / p gsql/#{node [ p o s t g r e s q l
] [ v e r s i o n ] } / data "
- d e f a u l t [ p o s t g r e s q l ] [ s e r v e r ] [ service_name ] = " p o s t g r e s q l #{
node [ p o s t g r e s q l ] [ v e r s i o n ] } "
Code 7.4 acmeco-postgresql/recipes/default.rb
Line 1 i n c l u d e _ r e c i p e p o s t g r e s q l : : yum_pgdg_postgresql
- i n c l u d e _ r e c i p e p o s t g r e s q l : : s e r v e r
What’s with the repetition of computed attributes in the wrapper? Well,
the values for default [ postgresql ][ client ][ packages’] and so on were calculated
when the attributes were loaded by the dependency, so to recompute them
based on the new value, we need to restate the expressions.
You could do all of this work in roles as well and if you do, the computed
attributes will be correctly resolved without this kind of repetition. This is
another reason that roles are still valuable.
You can take this one step further: suppose you wanted to then derive
the pg_hba.conf (the database access control file in PostgreSQL) through some
external mechanism that isn’t supported in the upstream cookbook. No prob-
lem: you can also set an attribute in recipe context, before the include_recipe
statements above:
Line 1 pg_hba_hash = call_some_method_to_get_a_hash ( )
- node . d e f a u l t [ p o s t g r e s q l ] [ pg_hba ] = pg_hba_hash
Again, in recipe context, there is no need to use normal or override priority
to achieve the desired effect.
Advanced Upstream Cookbook Modification
You can also use wrapper cookbooks to manipulate Chef’s Resource Collec-
tion. Put simply, the resource collection is the ordered list of resources, from
the recipes in your expanded run list, that are to be run on a node. You can
147
7.2. Knife Plugins
manipulate attributes of the resources in the resource collection. One common
use case for this is to change the template used by an upstream cookbook to
the caller’s cookbook. Again, suppose I’m using the PostgreSQL cookbook
but I really hate the sysconfig template that it uses. I can simply make my
own template inside the wrapper cookbook:
Code 7.5 acmeco-postgresql/templates/pgsql.sysconfig.erb
Line 1 PGDATA=<%= node [ p o s t g r e s q l ] [ d i r ] %>
- <% i f node [ p o s t g r e s q l ] [ c o n f i g ] . a t t r i b u t e ?( " por t " ) %>
- PGPORT=<%= node [ p o s t g r e s q l ] [ c o n f i g ] [ por t ] %>
- <% end %>
5 PGCHEFS=" Ohai " # or whatever changes you want to make
and «rewind» the resource collection definition after that resource has been
loaded by recipe [ postgresql :: server ] to change its cookbook attribute:
Code 7.6 acmeco-postgresql/recipes/default.rb
Line 1 i n c l u d e _ r e c i p e p o s t g r e s q l : : yum_pgdg_postgresql
- i n c l u d e _ r e c i p e " p o s t g r e s q l : : s e r v e r "
-
- r e s o u r c e s ( " t em plate [ / e t c / s y s c o n f i g / pgsq l /#{node [ p o s t g r e s q l ’ ] [
s e r v e r ’ ] [ service_name ] } ] " ) . cookbook acmeco p o s t g r e s q l
You can play this game with any other parameters to a previously defined
resource that you want to change. Because Chef uses a two-phase execution
model (compile, then converge), you can manipulate the results of that com-
pilation in many different ways before convergence happens.
Chef Rewind gem will also do this kind of manipulation.
Summary
Wrapper cookbooks allow you to modify the behavior of upstream cook-
books without forking them. These modifications can be very straightforward,
such as you might do with a role, except that they can contain logic to govern
the changes you want to make. Or the modifications can get quite advanced,
through altering the resources in the resource collection.
It’s useful to name your wrapper cookbooks with a standard prefix that
denotes your organization (e.g. «oc-» is what we use at Opscode). That
distinguishes your wrapper from the cookbook you’re wrapping.
Finally, you need not strictly adopt only wrapper cookbooks or only roles.
Used effectively, both roles and wrapper cookbooks give you a wealth of tools
to model your infrastructure effectively.
7.2 Knife Plugins
A Knife plugin is a set of one (or more) subcommands that can be added
to Knife to support additional functionality that is not built-in to the base set
148
7.2. Knife Plugins
of Knife subcommands. Many of the Knife plugins are built by members of
the Chef community and several of them are built and maintained by Chef.
A Knife plugin is installed to the ~/.chef/plugins/knife/ directory, from where it
can be run just like any other Knife subcommand.
the same common options used by Knife subcommands can also be used
by Knife plug-ins;
a Knife plugin can make authenticated API requests to the Chef server;
The following Knife plug-ins are maintained by Chef:
knife azure - Microsoft Azure is a cloud hosting platform from Microsoft
that provides virtual machines for Linux and Windows Server, cloud and
database services, and more. The knife azure subcommand is used to
manage API-driven cloud servers that are hosted by Microsoft Azure;
knife bluebox - Blue Box provides on-demand computing that is backed
by a proprietary cloud operating system. The knife bluebox subcommand
is used to manage API-driven cloud servers that are hosted by Blue Box;
knife ec2 - Amazon EC2 is a web service that provides resizable compute
capacity in the cloud, based on pre-configured operating systems and
virtual application software using Amazon Machine Images (AMI). The
knife ec2 subcommand is used to manage API-driven cloud servers that
are hosted by Amazon EC2;
knife eucalyptus - Eucalyptus is an infrastructure as a service (IaaS)
platform that supports hybrid-IaaS configurations that allow data to
move between hosted and on-premise data centers. The knife eucalyptus
subcommand is used to manage API-driven cloud servers that are hosted
by Eucalyptus;
knife google - Google Compute Engine is a cloud hosting platform that
offers scalable and flexible virtual machine computing. The knife google
subcommand is used to manage API-driven cloud servers that are hosted
by Google Compute Engine;
knife hp - HP Cloud Compute is a cloud hosting platform that provides
computing, storage, identity, and other services across private, managed,
and public clouds. The knife hp subcommand is used to manage API-
driven cloud servers that are hosted by HP Cloud Compute;
knife linode - Linode is a cloud hosting platform that provides virtual
private server hosting from the kernal and root access on up. The knife
linode subcommand is used to manage API-driven cloud servers that are
hosted by Linode;
knife openstack - The knife openstack subcommand is used to manage
API-driven cloud servers that are hosted by OpenStack;
knife rackspace - Rackspace is a cloud-driven platform of virtualized
servers that provide services for storage and data, platform and network-
ing, and cloud computing. The knife rackspace subcommand is used to
149
7.2. Knife Plugins
manage API-driven cloud servers that are hosted by Rackspace cloud
services;
knife terremark - Terremark is a cloud hosting platform that provides
cloud, IT infrastructure, and managed hosting services. The knife terre-
mark subcommand is used to manage API-driven cloud servers that are
hosted by Terremark;
knife windows - The knife windows subcommand is used to configure
and interact with nodes that exist on server and/or desktop machines
that are running Microsoft Windows. Nodes are configured using Win-
dows Remote Management, which allows native objects—batch scripts,
Windows PowerShell scripts, or scripting library variables—to be called
by external applications;
Examples
Let’s consider an example of using knife ec2 plugin. First of all you should
install it. You can use bundler or rubugems:
Line 1 $ gem i n s t a l l k n i f e ec2
The server create argument is used to create a new Amazon EC2 cloud
instance. This will provision a new image in Amazon EC2, perform a bootstrap
(using the SSH protocol), and then install the chef-client on the target system
so that it can be used to configure the node and to communicate with a Chef
server.
To launch a new Amazon EC2 instance with the «webserver» role (Ubuntu
Server 14.04 LTS (HVM), c3.large type):
Line 1 $ k n i f e e c2 s e r v e r c r e a t e r " r o l e [ web se rver ] " I amia6926dce f
c3 . l a r g e G www, d e f a u l t x ubuntu N s e r v e r 0 1 A awsa c c ess key
i d K awss e c r e t a c cess key S aws/ s e r v e r s . pem
Let’s look at the meaning of these options:
A KEY, −−awsaccesskeyid KEY - the access key identifier used with
Amazon EC2;
K SECRET, −−awssecretaccesskey SECRET - the secret access key for
the API endpoint used with Amazon EC2;
f FLAVOR, −−flavor FLAVOR - the name of the flavor that identifies the
hardware configuration of the server, including disk space, memory ca-
pacity, and CPU priority;
G X,Y,Z, −−groups X,Y,Z - a comma-separated list of security groups.
Not supported when using Amazon Virtual Private Cloud;
x USERNAME, −−sshuser USERNAME - the SSH user name;
r RUN_LIST, −−runlist RUN_LIST - a comma-separated list of roles
and/or recipes to be applied;
S KEY, −−sshkey KEY - the SSH key for the Amazon EC2 environment;
150
7.3. Chef Metal
Some of this keys possible to setup inside knife .rb:
Line 1 $ c a t ~ /. c h e f / k n i f e . rb
- k n i f e [ : a v a i l a b i l i t y _ z o n e ] = ENV[ EC2_AVAILABILITY_ZONE ]
- k n i f e [ : aws_access_key_id ] = ENV[ AWS_ACCESS_KEY_ID ]
- k n i f e [ : aws_secret_access_key ] = ENV[ AWS_SECRET_ACCESS_KEY’ ]
5 k n i f e [ : aws_ssh_key_id ] = " aws/ s e r v e r s . pem"
- k n i f e [ : image ] = " amia6926dce "
- k n i f e [ : f l a v o r ] = " c3 . l a r g e "
- k n i f e [ : chef_mode ] = " s o l o "
- k n i f e [ : reg i o n ] = ENV[ EC2_REGION ]
And now server create command has a different look:
Line 1 $ AWS_ACCESS_KEY_ID=awsa ccess keyi d AWS_SECRET_ACCESS_KEY=aws
s e c r e t a c c ess key k n i f e ec 2 s e r v e r c r e a t e r " r o l e [ w eb server ] "
G www, d e f a u l t x ubuntu N s e r v e r 0 1
The server list argument is used to find instances that are associated with a
Amazon EC2 account. The results may show instances that are not currently
managed by the Chef server.
Line 1 $ AWS_ACCESS_KEY_ID=awsa ccess keyi d AWS_SECRET_ACCESS_KEY=aws
s e c r e t a c c ess key k n i f e ec 2 s e r v e r l i s t
- I n s t a n c e ID Name Pub lic IP Privat e IP
Fl avor Image SSH Key S e c u r i t y Groups
IAM P r o f i l e State
- i xxx stag i n g f i x 2 5 . 1 9 6 . 1 9 5 . 4 1 1 1 . 3 1 . 5 3 . 1 1 8
m1 . medium amif 6 2 c d f 9 e some_key db , web
running
- i xxx Sta gin g : : Web
m1 . medium ami
3d 4 f f 2 5 4 some_key web stopp ed
The server delete argument is used to delete one or more nodes that are
running in the Amazon EC2 cloud. To find a specific cloud instance, use the
knife ec2 server list argument. Use the −−purge option to delete all associated
node and client objects from the Chef server or use the knife node delete and
knife client delete sub-commands to delete specific node and client objects.
Line 1 $ AWS_ACCESS_KEY_ID=awsa ccess keyi d AWS_SECRET_ACCESS_KEY=aws
s e c r e t a c c ess key k n i f e ec 2 s e r v e r d e l e t e i xxx
As you can see in these examples knife plugins simplify working with host-
ing cloud providers.
7.3 Chef Metal
Chef Metal solves the problem of repeatably creating machines and infras-
tructures in Chef. It has a plugin model that lets you write bootstrappers
for your favorite infrastructures, including VirtualBox, EC2, LXC, bare metal,
and many more. Combined with the power of Chef, Metal’s machine resource
helps you to describe, version, deploy and manage everything from simple to
complex clusters with a common set of tools.
151
7.3. Chef Metal
Examples
First of all, we should create file structure of project:
Line 1 $ t r e e R a .
- .
- | . c h e f
- | . . k n i f e . rb
5 | .ruby v e r s i o n
- |−− B e r k s f i l e
- |−− B e r k s f i l e . l o c k
- |−− c l u s t e r . rb
- |−− Gemfil e
10 |−− Gemfil e . l o c k
- |−− d e s t r o y _ a l l . rb
- vagrant . rb
File knife .rb contain setting for knife client:
Line 1 # This f i l e e x i s t s mainly to e nsur e we don t pick up k n i f e . rb from
anywhere e l s e
- local_mode true
- c o n f i g _ d i r "#{F i l e . expand_path ( . . , __FILE__) }/ " # Wherefore a r t
conf i g _dir , c h e f ?
-
5 # Chef 11. 1 4 b inds to " l o c a l h o s t " , which i n t e r f e r e s with por t
forw a rdi n g on IPv6 machines f o r some rea s on
- begin
- chef_ zer o . hos t 1 2 7 . 0 . 0 . 1
- r e s cue
- end
Chef Metal uses Chef Zero to create cluster of nodes.
Next we should install chef-zero rubygem. Also we should select which
provisioner we will use. For our example we will use Vagrant, what is why we
need install also chef-metal-vagrant rubygem. In this example uses bundler to
install all rubygems. This is content of Gemfile file:
Line 1 s ource " http : / / rubygems . org "
-
- gem c hef metal
- gem c hef metalvagrant
And run bundle command in terminal to install all rubygems.
In this example uses berkshelf to get all needed cookbooks. Content of
Berksfile file:
Line 1 s ource " http : / / a pi . b e r k s h e l f . com "
-
- cookbook apache2
- cookbook mysql
And run berks install && berks vendor cookbooks command in terminal to in-
stall and put all cookbooks to cookbooks directory.
All configuration will contains in files vagrant.rb (file contain configuration
for provisioner):
152
7.3. Chef Metal
Line 1 r e q u i r e chef_metal_vagrant
-
- vagrant_box p r e c i s e 6 4 do
- u r l http : / / f i l e s . vagrantup . com/ p r e c i s e 6 4 . box
5 end
-
- with_machine_options : vagrant _o ptions => {
- vm. box => p r e c i s e 6 4
- }
and cluster .rb (file contain configuration for our cluster):
Line 1 r e q u i r e chef_metal
-
- WEB_NODES = 2
-
5 1 . upto (WEB_NODES) do | i |
- machine " web#{i } " do
- tag web
- r e c i p e apache2
- co nve rge tru e
10 end
- end
-
- machine db do
- tag db
15 r e c i p e mysql : : s e r v e r
- co nve rge tru e
- end
By this setting we will create two web nodes and one database node. Now
we are ready to create our cluster of nodes:
Line 1 $ chef c l i e n t z vagrant . rb c l u s t e r . rb
- S t a r t i n g Chef Cli ent , v e r s i o n 1 1 . 1 4 . 2
- r e s o l v i n g cookbooks f o r run l i s t : [ ]
- S y nchr o nizi n g Cookbooks :
5 Compiling Cookbooks . . .
- [2014 0802T21 : 2 2 : 4 9 + 0 3 : 0 0 ] WARN: Node a l exey s mbp2 has an empty
run l i s t .
- Converging 2 r e s o u r c e s
- Recipe : @ r e c i p e _ f i l e s : : / Users / l e o / Downloads / chefmetal / vagrant . rb
- * vagrant_box [ p r e c i s e 6 4 ] a c t i o n c r e a t e ( up to d ate )
10 Recipe : @ r e c i p e _ f i l e s : : / Users / l e o / Downloads / chefmetal / c l u s t e r . rb
- * machine_batch [ d e f a u l t ] a c t i o n con verge
-
- . . .
-
15 Running h a n d l e r s complete
- Chef C l i e n t f i n i s h e d , 0/18 r e s o u r c e s updated i n 46. 72892 7972
sec ond s
If you need more web nodes you just need increase WEB_NODES variable
and run Chef Metal again.
To cleanup all nodes I created destroy_all.rb file:
153
7.4. Chef Sugar
Line 1 r e q u i r e chef_metal
-
- machine_batch do
- machines s e a r c h ( : node , * : * ) . map { | n | n . name }
5 a c t i o n : d e s t roy
- end
As a result you should see this output:
Line 1 $ chef c l i e n t z d e s t r o y _ a l l . rb
- S t a r t i n g Chef Cli ent , v e r s i o n 1 1 . 1 4 . 2
- r e s o l v i n g cookbooks f o r run l i s t : [ ]
- S y nchr o nizi n g Cookbooks :
5 Compiling Cookbooks . . .
- [2014 0802T21 : 4 6 : 2 6 + 0 3 : 0 0 ] WARN: Node a l exey s mbp2 has an empty
run l i s t .
- Converging 1 r e s o u r c e s
- Recipe : @ r e c i p e _ f i l e s : : / Users / l e o / Downloads / chefmetal / d e s t r o y _ a l l
. rb
- * machine_batch [ d e f a u l t ] a c t i o n d estroy
10 run vagrant d e s t r o y f db web1 web2 ( s t a t u s was running ,
running , running )
- d e l e t e node db at ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
- d e l e t e c l i e n t db at ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
- d e l e t e f i l e / Users / l e o /Downloads / che f metal / . c h e f /vms/db .vm
- d e l e t e node web1 at ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
15 d e l e t e c l i e n t web1 at ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
- d e l e t e f i l e / Users / l e o /Downloads / che f metal / . c h e f /vms/web1 .
vm
- d e l e t e node web2 at ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
- d e l e t e c l i e n t web2 at ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 8 8 9
- d e l e t e f i l e / Users / l e o /Downloads / che f metal / . c h e f /vms/web2 .
vm
20
- Running h a n d l e r s :
- Running h a n d l e r s complete
- Chef C l i e n t f i n i s h e d , 1/1 r e s o u r c e s updated in 32. 419881 s eco nds
As a result, we have DSL to describe our infrastructure (this is more «vis-
ible» for DevOps engineers) and can be used to describe, version, deploy and
manage everything from simple to complex clusters with a common set of tools.
Also Chef Metal is working on top of Chef, so it is very simple to extend and
modify your cluster configuration.
7.4 Chef Sugar
Chef Sugar is a rubygem and Chef recipe that includes series of helpful sugar
of the Chef core and other resources to make a cleaner, more lean recipe DSL,
enforce DRY principles, and make writing Chef recipes an awesome experience.
This is very useful library, which have huge amount of helpers and help for
developers to not «reinvent the wheel» inside own cookbooks.
154
7.4. Chef Sugar
Usage
First of all, you should add Chef Sugar inside own cookbook metadata.rb
file as dependency:
Line 1 depends c hef sugar
In order to use Chef Sugar in your Chef Recipes, you’ll first need to include
it:
Line 1 i n c l u d e _ r e c i p e ch efsugar
Alternatively you can put it in a base role or recipe and it will be included
subsequently.
Requiring the Chef Sugar Gem will automatically extend the Recipe DSL,
Chef::Resource and Chef::Provider with helpful convenience methods. If you are
working outside of the Recipe DSL, you can use the module methods instead
of the Recipe DSL. In general, the module methods have the same name as
their Recipe DSL counterparts, but require the node object as a parameter.
For example:
In recipe:
Line 1 # cookbook / r e c i p e s / d e f a u l t . rb
- do_something i f windows ?
In a library as a singleton:
Line 1 # cookbook / l i b r a r i e s / d e f a u l t . rb
- d e f only_on_windows(& bloc k )
- y i e l d i f Chef : : Sugar : : PlatformFamily . windows ? ( @node )
- end
In a library as a mixin:
Line 1 # cookbook / l i b r a r i e s / d e f a u l t . rb
- i n c l u d e Chef : : Sugar : : PlatformFamily
-
- d e f only_on_windows(& bloc k )
5 y i e l d i f windows ?( @node )
- end
Chef Sugar have huge amount of helper methods, more information about
its you can found in README. Examples:
Line 1 exe c u te b u i l d [ my bi nar y ] do
- command . . .
- not_ i f { _64_bit ? } # check system 64 b i t
- end
Line 1 i f ubuntu ? # system i s ubuntu
- exec u te aptget update
- end
Line 1 i f i n c l u d e s _ r e c i p e ? ( apache2 : : d e f a u l t ) # dete rmi n es i f the
c u r r e n t run c onte x t i n c l u d e s the r e c i p e
- apache_module my_module do
155
7.5. Summary
- # . . .
- end
5 end
7.5 Summary
Chef is very flexible DevOps tool. For it exists huge amount good vendor
cookbooks, libraries and extensions, which allow developers and administrators
use it as they see own infrastructure.
156
Bibliography
[1] Alexey Vasiliev aka leopard: Chef articles
http://leopard.in.ua/categories.html#chef-ref
[2] All about Chef ... http://docs.opscode.com/
[3] Doing Wrapper Cookbooks Right http://www.getchef.com/blog/2013/12/03/doing-
wrapper-cookbooks-right/
157