Google
A large group of web developers, otherwise ambivalent to Haskell, have recently found reason to learn the language thanks in part to the Snap web framework. Its really no surprise given Haskell's brilliance as an all purpose programming language and the effort the Snap team has put into providing great documentation. For those interested in exploring what it can do, the quickstart guide will get you up and running with snap in a few quick commands:
$ cabal update
$ cabal install snap
$ mkdir project
$ cd project
$ snap init
This, of course, will only work if you've already installed the Haskell Platform. Depending on your operating system and experience level this can be tedious if not challenging. With that in mind and my preference for using virtual machines when building web servers I've created a few Chef cookbooks for use with Vagrant that will hopefully make setting up and using Snap painless.

Setting up Vagrant

If you aren't already familiar with Vagrant, it's a virtual machine management tool for developers. Used in conjunction with Chef or Puppet provisioning tools, you can go from zero to a fully functioning, complex (reads multiple vm/server configuration) web application in one command. Among other things it helps to manage and standardize application dependencies, such as the Haskell Platform, by sequestering them inside a virtual machine where they can't collide on the host. If your skeptical about the time investment involved here, I promise, once you try it you'll never want to go back. You can find detailed directions on installing Vagrant and its dependencies in the getting started documentation. Aside: take note of all the awesome documentation being thrown around here!

Getting the project skeleton

First up you'll need to grab the the skeleton, which contains everything necessary for creating the vm. You can do this by cloning the git repository or by downloading a tar from github:
$ git clone git://github.com/johnbender/snap-skeleton.git
or
$ wget http://github.com/johnbender/snap-skeleton/tarball/master
After cloning/untaring you can rename the project directory, in which case you'll need to replace snap-skeleton in the following commands with whatever you choose.
$ cd snap-skeleton
$ vagrant up
Following the the up command you'll have to wait about 10 to 20 minutes depending on your internet connection for the platform to download and compile inside the vm. Once that's complete your snap skeleton directory will now be lifted into the virtual machine as an nfs shared folder so that you can start working on your Snap application. Its here that we can rejoin the Snap getting started guide with two differences. The `cabal update` has been performed for us during the vm creation and when we want to run snap or cabal commands we'll need to get inside the vm with `vagrant ssh` ( its important to note that you do NOT have to edit your haskell source in the vm as its in a shared directory with the host ).
$ vagrant ssh
vagrant@vagrantup:~$ cd /vagrant
vagrant@vagrantup:~$ snap init
vagrant@vagrantup:~$ cabal install
vagrant@vagrantup:~$ vagrant -p 8000
Initializing Heist/Impl... done.
Initializing Timer/Timer... done.
Listening on http://0.0.0.0:8000/
You'll also notice that application built from the initial snap source is named after its parent directory, which in the vm is /vagrant, the mount point for the project directory on your host machine. Changes made in this directory from the host side will obviously be available inside the vm and vice versa. You can now view the "it works" page at http://33.33.33.10:8000.

Vagrant workflow

Now that you've got a nice new virtual machine and everything necessary to dive into a new Snap application you'll need to know a few simple vagrant commands to save you some time. First and foremost all Vagrant commands must be run from within the project directory where the Vagrantfile resides. With that in mind, you've already seen the `up` command but if you want to get rid of your vm, a quick
$ vagrant destroy
will clean up for you. Keep in mind though that this means you'll have to start from scratch again when you want to play around with snap. Next, you can suspend and resume your vms with:
$ vagrant suspend
$ vagrant resume
Also of note are the provision and reload commands
$ vagrant provision
$ vagrant reload
If you decide you want to alter the cookbooks and recipes provided with the skeleton you can use the provision command to run chef solo for you. Alternately if you want to bounce the vm and run the cookbooks again you can use reload. With those commands in hand, lets make a small alteration to the files created by Snap's init and see how this all fits together. Open up Site.hs in your favorite text editor. It should be at /host/path/to/snap-skeleton/src/Site.hs and find the following snippet at the bottom:
echo :: Application ()
echo = do
    message <- decodedParam "stuff"
    heistLocal (bindString "message" (T.decodeUtf8 message)) $ render "echo"
  where
    decodedParam p = fromMaybe "" <$> getParam p
If you're not yet very familiar with Snap the echo handler takes the `stuff` get parameter and echo's it back to you. Lets change that so it ignores the get parameters:
echo :: Application ()
echo = heistLocal (bindString "message" "Vagrant + Snap => amazing") $ render "echo"
Alright, having edited the echo handler the application needs a recompile. First open up terminal at the snap-skeleton directory root then hop into the vm to compile and start the app:
$ vagrant ssh
vagrant@vagrantup:~$ cd /vagrant
vagrant@vagrantup:~$ cabal install
vagrant@vagrantup:~$ vagrant -p 8000
Initializing Heist/Impl... done.
Initializing Timer/Timer... done.
Listening on http://0.0.0.0:8000/
To see the alteration hit http://33.33.33.10/echo/foo. Generally you'll want at least one terminal logged into the vm while you're working. When you're done playing around just drop out of the vm and suspend it:
vagrant@vagrantup:~$ exit
$ vagrant suspend
The next time you want to start hacking just get to the snap-skeleton directory and fire up the vm with a `resume`.

Altering Your VM

The Vagrantfile thats included in the root of the snap-skeleton ( /path/to/snap-skeleton/Vagrantfile ) provides a couple of important configuration options to take note of and a quick overview of its structure will be useful.
Vagrant::Config.run do |config|
  config.vm.box = "base"
  config.vm.network("33.33.33.10")
  config.vm.customize do |vm|
    vm.name = "Snap"
    vm.memory_size = 768
  end

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = "cookbooks"
    chef.add_recipe "project"
    chef.json.merge!({ :ghc_version => '6.12.3',
                       :haskell_platform_version => '2010.2.0.0'})
  end
end
First, if you named the base box that you downloaded something other than base ( "lucid32" for example ) you can configure that by changing the value assigned to config.vm.box
config.vm.box = "base"
The ip thats defined is for the host network and the customization block allows you to change the attributes of the vm. Of particular note, if you want to change the name to better represent your project you can do that here.
config.vm.network("33.33.33.10")

config.vm.customize do |vm|
  vm.name = "Snap"
  vm.memory_size = 768
end
The last and most important configuration is for the provisioning tool, Chef.
config.vm.provision :chef_solo do |chef|
  chef.cookbooks_path = "cookbooks"
  chef.add_recipe "project"
  chef.json.merge!({ :ghc_version => '6.12.3',
                     :haskell_platform_version => '2010.2.0.0'})
end
The provisioner used here is Chef Solo which can, for more advanced users, be replaced with Chef Server or Puppet (see documentation). The first setting is the directory where the Chef cookbooks, that will be used to install and set up the vm, reside. The second is the name of the project recipe, which you are welcome to change as you get more comfortable with Chef.
chef.add_recipe "project"
The default recipe in the projects cookbook handles the inclusion of the rest of the coobooks ( cabal, haskell-platform, and apt). If you change the value here, you must change the directory accordingly in cookbooks:
$ ls cookbooks
apt  cabal  haskell-platform  project  README.md
Last but not least is the configuration passed to the Chef recipes. In this case I've defined the necessary versions for the current release of the haskell platform. In the future should these fall out of date they'll be easy to change!
chef.json.merge!({ :ghc_version => '6.12.3',
                   :haskell_platform_version => '2010.2.0.0'})

Published

05 Mar 2011

Tags