Chef For Developers part 3

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 Ubunutu VM running Apache and serving up the default website.

Part #2 got into creating a cookbook of my own, and evolved it whilst introducing PHP into the mix.

In this article I’ll get MySQL installed and integrated with PHP, and tidy up my own recipe.

Adding a database into the mix

1. Getting MySQL

Download mysql cookbook from the Opscode github repo into your “cookbooks” subdirecctory:

mysql

git clone https://github.com/opscode-cookbooks/mysql.git

Since this will be a server install instead of a client one you’ll also need to get OpenSSL:

openssl

git clone https://github.com/opscode-cookbooks/openssl.git

Now use Chef Solo to configure it by including the recipe reference and the mysql password in the Vagrantfile I’ve been using in the previous articles;

Vagrantfile

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
      },
      "mysql" => {
          "server_root_password" => "blahblah",
          "server_repl_password" => "blahblah",
          "server_debian_password" => "blahblah"
      },
      "mysite" => {
        "name" => "My AWESOME site",
        "web_root" => "/var/www/mysite"
      }
    }

    chef.cookbooks_path = ["cookbooks","site-cookbooks"]
    chef.add_recipe "mysql::server"
    chef.add_recipe "mysite"
  end
end

No need to explicitly reference OpenSSL; it’s in the “cookbooks” directory and since the mysql::server recipe references it it just gets pulled in.

If you run that now you’ll be able to ssh in and fool around with mysql using the user root and password as specified in the chef.json block.

vagrant ssh

and then

mysql -u root -p

and enter your password (“blahblah” in my case) to get into your mysql instance.

MySQL not doing very much

Now let’s make it do something. Using the mysql::ruby recipe it’s possible to orchestrate a lot of mysql functionality; this also relies on the build-essential cookbook, so download that into your “cookbooks” directory:

Build essential

git clone https://github.com/opscode-cookbooks/build-essential.git

To get some useful database abstraction methods we need the database cookbook:

Database

git clone https://github.com/opscode-cookbooks/database.git

The database cookbook gives a nice way of monkeying around with an RDBMS, making it possible to do funky things like:

mysql_connection = {:host => "localhost", :username => 'root',
                    :password => node['mysql']['server_root_password']}

mysql_database "#{node.mysite.database}" do
  connection mysql_connection
  action :create
end

to create a database.

Add the following to the top of the mysite/recipes/default.rb file:

include_recipe "mysql::ruby"

mysql_connection = {:host => "localhost", :username => 'root',
                    :password => node['mysql']['server_root_password']}

mysql_database node['mysite']['database'] do
  connection mysql_connection
  action :create
end

mysql_database_user "root" do
  connection mysql_connection
  password node['mysql']['server_root_password']
  database_name node['mysite']['database']
  host 'localhost'
  privileges [:select,:update,:insert, :delete]
  action [:create, :grant]
end

mysql_conn_args = "--user=root --password=#{node['mysql']['server_root_password']}"

execute 'insert-dummy-data' do
  command %Q{mysql #{mysql_conn_args} #{node['mysite']['database']} <<EOF
    CREATE TABLE transformers (name VARCHAR(32) PRIMARY KEY, type VARCHAR(32));
    INSERT INTO transformers (name, type) VALUES ('Hardhead','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Chromedome','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Brainstorm','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Highbrow','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Cerebros','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Fortress Maximus','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Chase','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Freeway','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Rollbar','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Searchlight','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Wideload','Throttlebot');
EOF}
  not_if "echo 'SELECT count(name) FROM transformers' | mysql #{mysql_conn_args} --skip-column-names #{node['mysite']['database']} | grep '^3$'"
end

and add in the new database variable in Vagrantfile:

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
      },
      "mysql" => {
      "server_root_password" => "blahblah",
      "server_repl_password" => "blahblah",
      "server_debian_password" => "blahblah"
      },
      "mysite" => {
        "name" => "My AWESOME site",
        "web_root" => "/var/www/mysite",
        "database" => "great_cartoons"
      }
    }

    chef.cookbooks_path = ["cookbooks","site-cookbooks"]
    chef.add_recipe "mysql::server"
    chef.add_recipe "mysite"
  end
end

Now we need a page to display that data, but we need to pass in the mysql password as a parameter. That means we need to use a template; create the file templates/default/robotsindisguise.php.erb with this content:

<?php
    $con = mysqli_connect("localhost","root", "<%= @pwd %>");
    if (mysqli_connect_errno($con))
    {
        die('Could not connect: ' . mysqli_connect_error());
    }

    $sql = "SELECT * FROM great_cartoons.transformers";
    $result = mysqli_query($con, $sql);

?>
    <table>
    <tr>
    <th>Transformer Name</th>
    <th>Type</th>
    </tr>
    <?php
    while($row = mysqli_fetch_array($result, MYSQL_ASSOC))
    {
    ?>
        <tr>
                <td><?php echo $row['name']?></td>
                <td><?php echo $row['type']?></td>
        </tr>
    <?php
    }//end while
    ?>
    </tr>
    </table>
<?php
mysqli_free_result($result);
mysqli_close($con);
?>

That line at the top might look odd:

$con = mysqli_connect("localhost","root", "<%= @pwd %>");

But bear in mind that it’s an ERB (Extended RuBy) file so gets processed by the ruby parser to generate the resulting file; the PHP processor only kicks in once the file is requested from a browser.

As such, if you kick off a vagrant up now and (eventually) vagrant ssh in, open /var/www/robotsindisguise.php in nano/vi and you’ll see the line

$con = mysqli_connect("localhost","root", "<%= @pwd %>");

has become

$con = mysqli_connect("localhost","root", "blahblahblah");

browsing to http://localhost:8080/robotsindisguise.php should give something like this:

Autobots: COMBINE!

2. Tidy it up a bit

Right now we’ve got data access stuff in the default.rb recipe, so let’s move that lot out; I’ve created the file /recipes/data.rb with these contents:

data.rb

include_recipe "mysql::ruby"

mysql_connection = {:host => "localhost", :username => 'root',
                    :password => node['mysql']['server_root_password']}

mysql_database node['mysite']['database'] do
  connection mysql_connection
  action :create
end

mysql_database_user "root" do
  connection mysql_connection
  password node['mysql']['server_root_password']
  database_name node['mysite']['database']
  host 'localhost'
  privileges [:select,:update,:insert, :delete]
  action [:create, :grant]
end

mysql_conn_args = "--user=root --password=#{node['mysql']['server_root_password']}"

execute 'insert-dummy-data' do
  command %Q{mysql #{mysql_conn_args} #{node['mysite']['database']} <<EOF
    CREATE TABLE transformers (name VARCHAR(32) PRIMARY KEY, type VARCHAR(32));
    INSERT INTO transformers (name, type) VALUES ('Hardhead','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Chromedome','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Brainstorm','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Highbrow','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Cerebros','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Fortress Maximus','Headmaster');
    INSERT INTO transformers (name, type) VALUES ('Chase','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Freeway','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Rollbar','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Searchlight','Throttlebot');
    INSERT INTO transformers (name, type) VALUES ('Wideload','Throttlebot');
EOF}
  not_if "echo 'SELECT count(name) FROM transformers' | mysql #{mysql_conn_args} --skip-column-names #{node['mysite']['database']} | grep '^3$'"
end

I’ve moved the php recipe references into recipes/webfiles.rb:

webfiles.rb

include_recipe "php"
include_recipe "php::module_mysql"

# -- 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

# copy in the mysql demo php file
 template "#{node.mysite.web_root}/robotsindisguise.php" do
    source "robotsindisguise.php.erb"
    variables ({
        :pwd => node.mysql.server_root_password
    })
    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

So /receipes/default.rb now looks like this:

default.rb

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"
include_recipe "mysite::data"

Summary

Over the past three articles we’ve automated the creation of a virtual environment via a series of code files, flat files, and template files, and a main script to pull it all together. The result is a full LAMP stack virtual machine. We also created a new website and pushed that on to the VM also.

All files used in this post can be found in the associated github repo.

Any comments or questions would be greatly appreciated, as would pull requests for improving my lame ruby and php skillz! (and lame css and html..)

2 thoughts on “Chef For Developers part 3

Leave a Reply

Your email address will not be published. Required fields are marked *