I’m continuing with my plan to create a series of articles for learning Chef from a developer perspective.
Part #1 gave an intro to Chef, Chef Solo, Vagrant, and Virtualbox. I also created my first Ubuntu VM running Apache and serving up the default website.
In this article I’ll get on to creating a cookbook of my own, and evolve it whilst introducing PHP into the mix.
Creating and evolving your own cookbook
1. Cook your own book
Downloaded configuration cookbooks live in the cookbooks subdirectory; this should be left alone as you can exclude it from version control knowing that the cookbooks are remotely hosted and can be downloaded as needed.
For your own ones you need to create a new directory; the convention for this has become to use site-cookbooks, but you can use whatever name you like as far as I can tell. You just need to add a reference to that directory in the Vagrantfile:
chef.cookbooks_path = ["cookbooks", "site-cookbooks", "blahblahblah"]
Within that new subdirectory you need to have, at a minimum, a recipes subdirectory with a default.rb ruby file which defines what your recipe does. Other key subdirectories are files (exactly that: files to be referenced/copied/whatever) and templates (ruby ERB templates which can be referenced to create a new file).
To create this default structure (for a cookbook called mysite) just use the one-liner:
mkdir -p site-cookbooks/mysite/{recipes,{templates,files}/default}
You’ll need to create two new files to spin up our new website; a favicon and a flat index html file. Create something simple and put them in the files/default/ directory (or use my ones).
Now in order for them to be referenced there needs to be a default.rb in recipes:
# -- Setup the website # create the webroot directory "#{node.mysite.web_root}" do mode 0755 end # copy in an index.html from mysite/files/default/index.html cookbook_file "#{node.mysite.web_root}/index.html" do source "index.html" mode 0755 end # copy in my usual favicon, just for the helluvit.. cookbook_file "#{node.mysite.web_root}/favicon.ico" do source "favicon.ico" mode 0755 end
This will create a directory for the website (the location of which needs to be defined in the chef.json section of the Vagrantfile), copy the specified files from files/default/ over, and set the permissions on them all so that the web process can access them.
You can also use the syntax:
directory node['mysite']['web_root'] do
in place of
directory "#{node.mysite.web_root}" do
So how will Apache know about this site? Better configure it with a conf file from a template; create a new file in templates/default/ called mysite.conf.erb:
<VirtualHost *:80> DocumentRoot <%= @params[:docroot] %> </VirtualHost>
And then reference it from the default.rb recipe file (add to the end of the one we just created, above):
web_app "mysite" do # where the website will live docroot "#{node.mysite.web_root}" # apache virtualhost definition template "mysite.conf.erb" end
That just calls the web_app method that exists within the Apache cookbook to create a new site called “mysite”, set the docroot to the same directory as we just created, and configure the virtual host to reference it, as configured in the ERB template.
The Vagrantfile now needs to become:
Vagrant.configure("2") do |config| config.vm.box = "precise32" config.vm.box_url = "http://files.vagrantup.com/precise32.box" config.vm.network :forwarded_port, guest: 80, host: 8080 config.vm.provision :chef_solo do |chef| chef.json = { "apache" => { "default_site_enabled" => false }, "mysite" => { "name" => "My AWESOME site", "web_root" => "/var/www/mysite" } } chef.cookbooks_path = ["cookbooks","site-cookbooks"] chef.add_recipe "apache2" chef.add_recipe "mysite" end end
Pro tip: be careful with quotes around the value for default_site_enabled; “false” == true whereas false == false, apparently.
Make sure you’ve destroyed your existing vagrant vm and bring this new one up, a decent one-liner is:
vagrant destroy --force && vagrant up
You should see a load of references to your new cookbook in the output and hopefully once it’s finished you’ll be able to browse to http://localhost:8080 and see something as GORGEOUS as:
2. Skipping the M in LAMP, Straight to the P: PHP
Referencing PHP
Configure your code to bring in PHP; a new recipe needs to be referenced as a module of Apache:
chef.add_recipe "apache2::mod_php5"
It’s probably worth mentioning that
add_recipe "apache"
actually means
add_recipe "apache::default"
As such, mod_php5 is a recipe file itself, much like default.rb is; you can find it in the Apache cookbook under cookbooks/apache2/recipes/mod_php5.rb and all it does is call the approriate package manager to install the necessary libraries.
You may find that you receive the following error after adding in that recipe reference:
apt-get -q -y install libapache2-mod-php5=5.3.10-1ubuntu3.3 returned 100, expected 0
To get around this you need to add in some simple apt-get housekeeping before any other provisioning:
config.vm.provision :shell, :inline => "apt-get clean; apt-get update"
PHPInfo
Let’s make a basic phpinfo page to show that PHP is in there and running. To do this you could create a new file and just whack in a call to phpinfo(), but I’m going to create a new template so we can pass in a page title for it to use (create your own, or just use mine):
<html> <head> <title><%= @title %></title> .. snip.. </head> <body> <h1><%= @title %></h1> <div class="description"> <?php phpinfo( ); ?> </div> .. snip .. </body> </html>
The default.rb recipe now needs a new section to create a file from the template:
# use a template to create a phpinfo page (just creating the file and passing in one variable) template "#{node.mysite.web_root}/phpinfo.php" do source "testpage.php.erb" mode 0755 variables ({ :title => node.mysite.name }) end
Destroy, rebuild, and browse to http://localhost:8080/phpinfo.php:
Notice the heading and the title of the tab are set to the values passed in from the Vagrantfile.
3. Refactor the Cookbook
We can actually put the add_recipe calls inside of other recipes using include_recipe, so that the dependencies are explicit; no need to worry about forgetting to include apache in the Vagrantfile if you’re including it in your recipe itself.
Let’s make default.rb responsible for the web app itself, and make a new recipe for creating the web files; create a new webfiles.rb in recipes/mysite and move the file related stuff in there:
webfiles.rb
# -- Setup the website # create the webroot directory "#{node.mysite.web_root}" do mode 0755 end # copy in an index.html from mysite/files/default/index.html cookbook_file "#{node.mysite.web_root}/index.html" do source "index.html" mode 0755 end # copy in my usual favicon, just for the helluvit.. cookbook_file "#{node.mysite.web_root}/favicon.ico" do source "favicon.ico" mode 0755 end # use a template to create a phpinfo page (just creating the file and passing in one variable) template "#{node.mysite.web_root}/phpinfo.php" do source "testpage.php.erb" mode 0755 variables ({ :title => node.mysite.name }) end
default.rb now looks like
include_recipe "apache2" include_recipe "apache2::mod_php5" # call "web_app" from the apache recipe definition to set up a new website web_app "mysite" do # where the website will live docroot "#{node.mysite.web_root}" # apache virtualhost definition template "mysite.conf.erb" end include_recipe "mysite::webfiles"
And Vagrantfile now looks like:
Vagrant.configure("2") do |config| config.vm.box = "precise32" config.vm.box_url = "http://files.vagrantup.com/precise32.box" config.vm.network :forwarded_port, guest: 80, host: 8080 config.vm.provision :shell, :inline => "apt-get clean; apt-get update" config.vm.provision :chef_solo do |chef| chef.json = { "apache" => { "default_site_enabled" => false }, "mysite" => { "name" => "My AWESOME site", "web_root" => "/var/www/mysite" } } chef.cookbooks_path = ["cookbooks","site-cookbooks"] chef.add_recipe "mysite" end end
The add_recipes are now include_recipes moved to default.rb, the file related stuff is in webfiles.rb and there’s an include_recipe to reference this new file:
include_recipe "mysite::webfiles"
Why the refactoring is important!
Well, refactoring is a nice, cathartic, thing to do anyway. But there’s also a specific reason for doing it here: once we move from using Chef Solo to Grown Up Chef (aka Hosted Chef) the Vagrantfile won’t be used anymore.
As such, moving the logic out of the Vagrantfile (e.g., add_recipe calls) and into our own cookbook (e.g. include_recipe calls) will allow us to use our same recipe in both Chef Solo and also Hosted Chef.
Next up
We’ll be getting stuck in to MySQL integration and evolving a slightly more dynamic recipe.
All files used in this post can be found in the associated github repo.
One thought on “Chef For Developers part 2”