• Vagrant & Chef

             ... for Drupal developers

    Alex Dergachev | Co-founder of Evolving Web

    @dergachev on github | @dergachev on twitter

  • Vagrant is…

    • A wrapper on VBoxManage,
    • A workflow,
    • A way of life.
  • Provisioning

    Installing software on the VM

  • Ruby for Chef

    Just enough not to freak out

  • Writing a cookbook

  • Contributed cookbooks

    Before writing your own, check out these community-contributed cookbooks that will help you install a LAMP environment and related packages, deploy your Drupal codebase, and generally customize the server to your needs.

  • Additional stuff

    Useful tools and extensions to Vagrant and Chef.

  • Installing Vagrant

    Run these GUI installers:

    Both are simple, reliable, and work on Windows/Mac/Linux

  • Base boxes

    Just a VM image with:

    • “vagrant” user & ssh keys
    • virtualbox guest additions
    • installed ruby, chef, puppet
  • Vagrant workflow

    vagrant box add precise64
      http://files.vagrantup.com/precise64.box
    vagrant init
    vagrant up # --no provision
    vagrant reload
    vagrant provision
    vagrant destroy --force && vagrant up
    vagrant ssh  # -p -- -l alex
    ls /vagrant # inside the VM
    vagrant package # outputs ./package.box
    vagrant snap take # requires vagrant-snap plugin
  • Vagrantfile config

    config.vm.forward_port 80, 4567
    config.vm.box = "precise64"
    config.vm.share_folder "foo", "/guest/path", 
      "/host/path", :nfs => true
    config.vm.network :hostonly, "10.11.12.13"
     # config.vm.network :bridged
    config.vm.customize 
      ["modifyvm", :id, "--memory", 1024]
    config.vm.provision :shell, 
      :inline => "echo foo > /vagrant/test"
     # support for multiple VMs
  • Supported provisioners

    • shell
    • chef-solo
    • chef
    • Puppet
  • Shell provisioner

    config.vm.provision :shell, 
      :inline => "apt-get -y update"
    
    config.vm.provision :shell, 
      :inline => "sudo apt-get -y vim git"
  • Chef-solo provisioner

    config.vm.provision :chef_solo do |chef|
      chef.cookbooks_path = 
        ["cookbooks", "~/company/cookbooks"]
      chef.add_recipe("apache")
      chef.add_recipe("php")
      chef.json = {
        :load_limit => 42,
        :chunky_bacon => true
      }
    end

    See Vagrant chef_solo docs and iterative chef tutorial.

  • Methods

    def double(num)
      num * 2
    end
    
    double 33 # => 66
  • Ruby Strings

    :bob == 'bob'.to_sym
    
    %w{git vim mysql} == ['git', 'vim', 'mysql']
    
    "head #{var} tail" == "head "+var+" tail"
  • Ruby dictionaries

    options = {
      :deploy_drupal => { 
        :apache_group => 'vagrant', 
        :sql_load_file => '/vagrant/db/fga.sql.gz',
        :source =>  "/vagrant/public/fga/www",
      },
      :mysql => {
        :server_root_password => "root",
      },
    }
  • DSL… wtf?

    template "/etc/mysql/grants.sql" do
      source "grants.sql.erb"
      owner "root"
      group "root"
    end
    template("/etc/mysql/grants.sql", function() {  
      this.source("grants.sql.erb");
      this.owner("root");
      this.group("root");
    });
  • Cookbooks and recipes

    A cookbooks contains recipes, which are very simple ruby scripts that install software or otherwise configure the VM.

    Recipes read data from attributes, and call built-in or custom resources, which are helper methods that do all the work.

  • Cookbook structure

  • Example Cookbook

    Adapted from @opscode/getting-started:

    • recipes/
      • default.rb
      • other_recipe.rb
    • attributes/
      • default.rb
    • templates/
      • httpd.conf.erb
    • metadata.rb
    • README.md
  • Resources

    • Recipes are ruby scripts, but with minimal code.
    • Recipes specify which system configuration Resources chef should execute
    • Resource implementations (called Providers) actually do the work.
    • Simplifying, recipes contain “API calls” to resources, with optional arguments.
  • Install a template

    template '/var/www/sites/default/settings.php' do
      # action :create
      # action :create_if_missing
      source "settings.php.erb"
      variables ( {
        :user => 'root',
        :pass => node[:mysql]['root_password'],
        :name => 'drupalsite_v1',
      })
    end
  • Install packages

    package "tar" do
      action :install
    end
    
    package %w{vim git}
    
    php_pear "uploadprogress"
    
    gem_package "syntax"
  • Execute bash

    execute "download drupal" do
      cwd '/var/www'
      command "drush dl drupal-7.20"
      creates '/var/www/drupal-7.20/index.php'
    end

    creates tells chef not to re-execute the resource if index.php is already in place.

  • Deploy from git

    deploy "/var/www" do 
      repo "git@github.com/mycompany/project"
      user "www-data"
      group "www-data"
      revision "master"
      action :sync # default value, also :checkout
    end
  • Controlling services

     # runs /etc/init.d/apache2 start|stop|...
    service "apache2" do
      supports :status => true, 
        :restart => true, :reload => true
      action [ :enable, :start ]
    end
  • Install a user

    user "sam" do
         home "/home/sam"
         shell "/bin/zsh"
         comment "Sam loves DevOps"
         action :create
         password :$1$pfFfDG3M$22vsMsPnn93ZnuodI86Ec0
    end
  • Add a vhost config

    web_app 'drupalcampottawa' do
      template "web_app.conf.erb"
      port 80
      server_name 'drupalcampottawa.com'
      docroot '/var/www/dcampottawa'
      notifies :restart, 
        resources("service[apache2]"), 
        :delayed
    end

    web_app is custom resource defined in the apache2 cookbook

  • Example recipe

    Putting it all together, you should be able to understand this example recipe

{"cards":[{"_id":"2bb34b2e140f13ebd2000001","treeId":"2b8eba0efe143b2452000001","seq":1,"position":0.375,"parentId":null,"content":"# Vagrant & Chef\n ... for Drupal developers\n\n__Alex Dergachev__ | Co-founder of [Evolving Web](http://evolvingweb.ca)\n\n[@dergachev](https://github.com/dergachev) on github | [@dergachev](https://twitter.com/dergachev) on twitter"},{"_id":"2b98dcf3e2d52f63b3000003","treeId":"2b8eba0efe143b2452000001","seq":1,"position":0.75,"parentId":null,"content":"# Vagrant is...\n\n* A wrapper on VBoxManage,\n* A workflow,\n* A way of life."},{"_id":"2b98deece2d52f63b3000004","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1,"parentId":"2b98dcf3e2d52f63b3000003","content":"## Installing Vagrant\n\nRun these GUI installers:\n\n* [Download Virtualbox](https://www.virtualbox.org/wiki/Downloads)\n* [Download Vagrant](http://downloads.vagrantup.com/)\n\nBoth are simple, reliable, and work on Windows/Mac/Linux"},{"_id":"2b98eb8ae2d52f63b3000005","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2,"parentId":"2b98dcf3e2d52f63b3000003","content":"## Vagrant Docs\n\n* [vagrantup.com](http://vagrantup.com)\n* [docs.vagrantup.com/v1/docs/](http://docs.vagrantup.com/v1/docs/)\n* [docs.vagrantup.com/v1/docs/getting-started](http://docs.vagrantup.com/v1/docs/getting-started)\n"},{"_id":"2b98ee2ae2d52f63b3000006","treeId":"2b8eba0efe143b2452000001","seq":1,"position":3,"parentId":"2b98dcf3e2d52f63b3000003","content":"## Base boxes\n\nJust a VM image with:\n\n- \"vagrant\" user & ssh keys\n- virtualbox guest additions\n- installed ruby, chef, puppet"},{"_id":"2bb31432bd6088aefc000007","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1,"parentId":"2b98ee2ae2d52f63b3000006","content":"### Where to find base boxes\n\n* Community boxes from http://www.vagrantbox.es/ \n* official builds have URLs like files.vagrantup.com/*.box"},{"_id":"2bb315e4bd6088aefc000008","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2,"parentId":"2b98ee2ae2d52f63b3000006","content":"### Base box docs\n\n* vagrantup.com doc on [base boxes](http://docs.vagrantup.com/v1/docs/base_boxes.html)\n* Tutorial on [manually building a base box](https://github.com/fespinoza/checklist_and_guides/wiki/Creating-a-vagrant-base-box-for-ubuntu-12.04-32bit-server)\n* [Vewee](https://github.com/jedi4ever/vewee) automates box creation.\n * [veewee tutorial](http://seletz.github.com/blog/2012/01/17/creating-vagrant-base-boxes-with-veewee/)"},{"_id":"2b99724fe2d52f63b3000008","treeId":"2b8eba0efe143b2452000001","seq":1,"position":5,"parentId":"2b98dcf3e2d52f63b3000003","content":"## Vagrant workflow\n\n```bash\nvagrant box add precise64\n http://files.vagrantup.com/precise64.box\nvagrant init\nvagrant up # --no provision\nvagrant reload\nvagrant provision\nvagrant destroy --force && vagrant up\nvagrant ssh # -p -- -l alex\nls /vagrant # inside the VM\nvagrant package # outputs ./package.box\nvagrant snap take # requires vagrant-snap plugin\n```"},{"_id":"2b999826e2d52f63b3000009","treeId":"2b8eba0efe143b2452000001","seq":1,"position":6,"parentId":"2b98dcf3e2d52f63b3000003","content":"## Vagrantfile config\n\n```ruby\nconfig.vm.forward_port 80, 4567\nconfig.vm.box = \"precise64\"\nconfig.vm.share_folder \"foo\", \"/guest/path\", \n \"/host/path\", :nfs => true\nconfig.vm.network :hostonly, \"10.11.12.13\"\n # config.vm.network :bridged\nconfig.vm.customize \n [\"modifyvm\", :id, \"--memory\", 1024]\nconfig.vm.provision :shell, \n :inline => \"echo foo > /vagrant/test\"\n # support for multiple VMs\n```"},{"_id":"2b99a998f34f1278af000001","treeId":"2b8eba0efe143b2452000001","seq":1,"position":0.875,"parentId":null,"content":"# Provisioning\n\nInstalling software on the VM"},{"_id":"2b99b067f34f1278af000003","treeId":"2b8eba0efe143b2452000001","seq":1,"position":0.5,"parentId":"2b99a998f34f1278af000001","content":"## Supported provisioners\n\n* shell\n* chef-solo\n* chef\n* Puppet"},{"_id":"2b99abe3f34f1278af000002","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1,"parentId":"2b99a998f34f1278af000001","content":"## Shell provisioner\n\n```ruby\nconfig.vm.provision :shell, \n :inline => \"apt-get -y update\"\n\nconfig.vm.provision :shell, \n :inline => \"sudo apt-get -y vim git\"\n```"},{"_id":"2b99b339f34f1278af000004","treeId":"2b8eba0efe143b2452000001","seq":1,"position":3,"parentId":"2b99a998f34f1278af000001","content":"## Chef-solo provisioner\n\n```ruby\nconfig.vm.provision :chef_solo do |chef|\n chef.cookbooks_path = \n [\"cookbooks\", \"~/company/cookbooks\"]\n chef.add_recipe(\"apache\")\n chef.add_recipe(\"php\")\n chef.json = {\n :load_limit => 42,\n :chunky_bacon => true\n }\nend\n```\n\nSee [Vagrant chef_solo docs]( http://docs.vagrantup.com/v1/docs/provisioners/chef_solo.html) and [iterative chef](http://fnichol.github.com/iterative_chef/) tutorial."},{"_id":"2b8ebb6efe143b2452000002","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1,"parentId":null,"content":"# Ruby for Chef\n\nJust enough not to freak out"},{"_id":"2b8efa4ffe143b2452000008","treeId":"2b8eba0efe143b2452000001","seq":1,"position":0.25,"parentId":"2b8ebb6efe143b2452000002","content":"## Methods\n\n```ruby\ndef double(num)\n num * 2\nend\n\ndouble 33 # => 66\n```"},{"_id":"2b8ed2d0fe143b2452000007","treeId":"2b8eba0efe143b2452000001","seq":1,"position":0.5,"parentId":"2b8ebb6efe143b2452000002","content":"## Ruby Strings\n\n```ruby\n:bob == 'bob'.to_sym\n\n%w{git vim mysql} == ['git', 'vim', 'mysql']\n\n\"head #{var} tail\" == \"head \"+var+\" tail\"\n```"},{"_id":"2b8f0c0efe143b2452000009","treeId":"2b8eba0efe143b2452000001","seq":1,"position":0.75,"parentId":"2b8ebb6efe143b2452000002","content":"## Ruby dictionaries\n\n```ruby\noptions = {\n :deploy_drupal => { \n :apache_group => 'vagrant', \n :sql_load_file => '/vagrant/db/fga.sql.gz',\n :source => \"/vagrant/public/fga/www\",\n },\n :mysql => {\n :server_root_password => \"root\",\n },\n}\n```\n"},{"_id":"2b8ece4ffe143b2452000006","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1,"parentId":"2b8ebb6efe143b2452000002","content":"## DSL... wtf?\n\n```ruby\ntemplate \"/etc/mysql/grants.sql\" do\n source \"grants.sql.erb\"\n owner \"root\"\n group \"root\"\nend\n```\n\n```javascript\ntemplate(\"/etc/mysql/grants.sql\", function() { \n this.source(\"grants.sql.erb\");\n this.owner(\"root\");\n this.group(\"root\");\n});\n```"},{"_id":"2b8ebe9afe143b2452000003","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2,"parentId":null,"content":"# Writing a cookbook\n\n"},{"_id":"2bb6482db70d71b820000003","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1.25,"parentId":"2b8ebe9afe143b2452000003","content":"## Cookbooks and recipes \n\nA __cookbooks__ contains __recipes__, which are _very simple_ ruby scripts that install software or otherwise configure the VM. \n\nRecipes read data from __attributes__, and call built-in or custom __resources__, which are helper methods that do all the work. "},{"_id":"2bb644a8b70d71b820000001","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1.875,"parentId":"2b8ebe9afe143b2452000003","content":"## Cookbook structure\n\n![](http://dl-web.dropbox.com/u/29440342/screenshots/APTIAH-2013.2.22-16.51.png)\n"},{"_id":"2b8f142efe143b245200000a","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2.5,"parentId":"2b8ebe9afe143b2452000003","content":"## Example Cookbook\n\nAdapted from [@opscode/getting-started](https://github.com/opscode-cookbooks/getting-started):\n\n* _recipes/_\n * default.rb\n * other_recipe.rb\n* _attributes/_\n * default.rb\n* _templates/_\n * httpd.conf.erb\n* metadata.rb\n* README.md"},{"_id":"2bb679d3b70d71b820000004","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2.875,"parentId":"2b8ebe9afe143b2452000003","content":"## Resources\n\n* __Recipes__ are ruby scripts, but with minimal code.\n* Recipes specify which system configuration __Resources__ chef should execute\n* Resource implementations (called __Providers__) actually do the work.\n* Simplifying, recipes contain \"API calls\" to resources, with optional arguments."},{"_id":"2b8f1ad0fe143b245200000c","treeId":"2b8eba0efe143b2452000001","seq":1,"position":3,"parentId":"2b8ebe9afe143b2452000003","content":"## Install a template\n\n```\ntemplate '/var/www/sites/default/settings.php' do\n # action :create\n # action :create_if_missing\n source \"settings.php.erb\"\n variables ( {\n :user => 'root',\n :pass => node[:mysql]['root_password'],\n :name => 'drupalsite_v1',\n })\nend\n```"},{"_id":"2b8f28bcfe143b245200000d","treeId":"2b8eba0efe143b2452000001","seq":1,"position":4,"parentId":"2b8ebe9afe143b2452000003","content":"## Install packages\n\n```\npackage \"tar\" do\n action :install\nend\n\npackage %w{vim git}\n\nphp_pear \"uploadprogress\"\n\ngem_package \"syntax\"\n```\n"},{"_id":"2b8f2ebafe143b245200000e","treeId":"2b8eba0efe143b2452000001","seq":1,"position":5,"parentId":"2b8ebe9afe143b2452000003","content":"## Execute bash\n\n```\nexecute \"download drupal\" do\n cwd '/var/www'\n command \"drush dl drupal-7.20\"\n creates '/var/www/drupal-7.20/index.php'\nend\n```\n\n__creates__ tells chef not to re-execute the resource if index.php is already in place."},{"_id":"2b8f53dcfe143b245200000f","treeId":"2b8eba0efe143b2452000001","seq":1,"position":6,"parentId":"2b8ebe9afe143b2452000003","content":"## Deploy from git\n\n```\ndeploy \"/var/www\" do \n  repo \"git@github.com/mycompany/project\"\n  user \"www-data\"\n group \"www-data\"\n revision \"master\"\n action :sync # default value, also :checkout\nend\n```"},{"_id":"2b8f760bfe143b2452000011","treeId":"2b8eba0efe143b2452000001","seq":1,"position":6.5,"parentId":"2b8ebe9afe143b2452000003","content":"## Controlling services\n\n```\n # runs /etc/init.d/apache2 start|stop|...\nservice \"apache2\" do\n  supports :status => true, \n :restart => true, :reload => true\n  action [ :enable, :start ]\nend\n```"},{"_id":"2b8f171bfe143b245200000b","treeId":"2b8eba0efe143b2452000001","seq":1,"position":8,"parentId":"2b8ebe9afe143b2452000003","content":"## Install a user\n\n```\nuser \"sam\" do\n     home \"/home/sam\"\n     shell \"/bin/zsh\"\n     comment \"Sam loves DevOps\"\n     action :create\n password :$1$pfFfDG3M$22vsMsPnn93ZnuodI86Ec0\nend\n```"},{"_id":"2b8f702cfe143b2452000010","treeId":"2b8eba0efe143b2452000001","seq":1,"position":8.5,"parentId":"2b8ebe9afe143b2452000003","content":"## Add a vhost config\n\n```ruby\nweb_app 'drupalcampottawa' do\n template \"web_app.conf.erb\"\n port 80\n server_name 'drupalcampottawa.com'\n docroot '/var/www/dcampottawa'\n notifies :restart, \n resources(\"service[apache2]\"), \n :delayed\nend\n```\n\n__web_app__ is custom resource defined in the apache2 cookbook"},{"_id":"2bd0198e456df97a95000001","treeId":"2b8eba0efe143b2452000001","seq":1,"position":8.75,"parentId":"2b8ebe9afe143b2452000003","content":"## Example recipe\n\nPutting it all together, you should be able to understand this [example recipe](https://github.com/fespinoza/checklist_and_guides/blob/master/chef%20solo%20guide/cookbooks/main/recipes/default.rb)"},{"_id":"2b99bb98f34f1278af000005","treeId":"2b8eba0efe143b2452000001","seq":1,"position":9,"parentId":"2b8ebe9afe143b2452000003","content":"## More about recipes and resources\n\n* [docs.opscode.com/chef/resources.html](http://docs.opscode.com/chef/resources.html) (READ ALL OF THIS)\n* [wiki.opscode.com/display/chef/Chef+Basics](http://wiki.opscode.com/display/chef/Chef+Basics)\n* [docs.opscode.com/essentials_cookbooks.html](http://docs.opscode.com/essentials_cookbooks.html)\n* [wiki.opscode.com/display/chef/Resources+and+Providers](http://wiki.opscode.com/display/chef/Resources+and+Providers)\n\n"},{"_id":"2bb2e08ebd6088aefc000006","treeId":"2b8eba0efe143b2452000001","seq":1,"position":3.5,"parentId":null,"content":"# Contributed cookbooks\n\nBefore writing your own, check out these community-contributed cookbooks that will help you install a LAMP environment and related packages, deploy your Drupal codebase, and generally customize the server to your needs."},{"_id":"2bb398a8140f13ebd2000004","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1.5,"parentId":"2bb2e08ebd6088aefc000006","content":"## Drupal stack cookbooks\n\n__Services:__\n\n* [apache2](https://github.com/opscode-cookbooks/apache2) and [php](https://github.com/opscode-cookbooks/php) and [mysql](https://github.com/opscode-cookbooks/mysql)\n* [memcached](https://github.com/opscode-cookbooks/memcached) and [nginx](https://github.com/opscode-cookbooks/nginx) and [varnish](https://github.com/opscode-cookbooks/varnish)\n\n__Supporting:__\n\n* [apt](https://github.com/opscode-cookbooks/apt)\n* [git](https://github.com/opscode-cookbooks/git)\n* [database](https://github.com/opscode-cookbooks/database)\n* [cron](https://github.com/opscode-cookbooks/cron)\n\n__Extra :__\n\n* [phpmyadmin](https://github.com/opscode-cookbooks/phpmyadmin) and [imagemagick](https://github.com/opscode-cookbooks/imagemagick)\n* [webgrind](https://github.com/opscode-cookbooks/webgrind) and [@msonnabaum/chef-xhprof](https://github.com/msonnabaum/chef-xhprof)\n* [drush_make](https://github.com/opscode-cookbooks/drush_make) and [@msonnabaum/chef-drush](https://github.com/msonnabaum/chef-drush)"},{"_id":"2bb2552cb3df0c0870000001","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2.25,"parentId":"2bb2e08ebd6088aefc000006","content":"## Drupal deployment cookbooks\n\n- [@msonnabaum/chef-drush](\nhttps://github.com/msonnabaum/chef-drush)\n- [@dergachev/vagrant_drupal](https://github.com/dergachev/vagrant_drupal) - work in progress!\n- [@myplanetdigital/vagrant-ariadne](https://github.com/myplanetdigital/vagrant-ariadne)\n- [@xforty/chef-drupal](https://github.com/xforty/chef-drupal)\n- [d.o/vagrant](http://drupal.org/project/vagrant)\n- [d.o/drush-vagrant](http://drupal.org/project/drush-vagrant)"},{"_id":"2bb3c040c43ebcda23000001","treeId":"2b8eba0efe143b2452000001","seq":1,"position":3,"parentId":"2bb2e08ebd6088aefc000006","content":"## User-provisioning cookbooks\n\n* [users](https://github.com/opscode-cookbooks/users)\n* [ssh_known_hosts](https://github.com/opscode-cookbooks/ssh_known_hosts)\n* [@fnichol/chef-homesick](https://github.com/fnichol/chef-homesick)\n* [sudo](https://github.com/opscode-cookbooks/sudo)"},{"_id":"2bb3c17cc43ebcda23000002","treeId":"2b8eba0efe143b2452000001","seq":1,"position":4,"parentId":"2bb2e08ebd6088aefc000006","content":"## Even more cookbooks\n\n* [tomcat](https://github.com/opscode-cookbooks/tomcat)\n* [tftp](https://github.com/opscode-cookbooks/tftp)\n* [homebrew](https://github.com/opscode-cookbooks/homebrew)\n* [postfix](https://github.com/opscode-cookbooks/postfix)\n* [@fnichol/chef-rvm](https://github.com/fnichol/chef-rvm)"},{"_id":"2bb3eaa1fd3b235f6a000001","treeId":"2b8eba0efe143b2452000001","seq":1,"position":5,"parentId":"2bb2e08ebd6088aefc000006","content":"## Official cookbook repositories\n\n* Repo of all [Opscode maintained cookbooks](https://github.com/opscode-cookbooks)\n* Repo of all [community contributed cookbooks](http://community.opscode.com/cookbooks)"},{"_id":"2bb260dcb3df0c0870000002","treeId":"2b8eba0efe143b2452000001","seq":1,"position":4,"parentId":null,"content":"# Additional stuff\n\nUseful tools and extensions to Vagrant and Chef."},{"_id":"2bb26168b3df0c0870000003","treeId":"2b8eba0efe143b2452000001","seq":1,"position":1,"parentId":"2bb260dcb3df0c0870000002","content":"## Testing\n\n* [Travis](https://travis-ci.org)\n* [Food critic](http://acrmp.github.com/foodcritic/)\n* [Minitest](https://github.com/calavera/minitest-chef-handler)"},{"_id":"2bb2647db3df0c0870000004","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2,"parentId":"2bb260dcb3df0c0870000002","content":"## Cookbook managers\n\nThese tools automatically download dependent cookbooks:\n\n* [Librarian](https://github.com/applicationsonline/librarian)\n* [Berkshelf](http://berkshelf.com/)\n\nSee this [berkshelf tutorial](http://vialstudios.com/guide-authoring-cookbooks.html)"},{"_id":"2bb28d95bd6088aefc000002","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2.25,"parentId":"2bb260dcb3df0c0870000002","content":"## Vagrant Plugins\n\n* Build your own base boxes: [@jedi4ever/veewee](https://github.com/jedi4ever/veewee)\n* Manage virtualbox snapshots: [@jedi4ever/sahara](https://github.com/jedi4ever/sahara) and [@t9md/vagrant-snap](https://github.com/t9md/vagrant-snap)\n* [@mosaicxm/vagrant-hostmaster](https://github.com/mosaicxm/vagrant-hostmaster) for managing /etc/hosts\n* List of [Vagrant Plugins](https://github.com/mitchellh/vagrant/wiki/Available-Vagrant-Plugins)\n* Docs on [writing Vagrant plugins](http://docs.vagrantup.com/v1/docs/extending/)"},{"_id":"2bb278cab3df0c0870000007","treeId":"2b8eba0efe143b2452000001","seq":1,"position":2.5,"parentId":"2bb260dcb3df0c0870000002","content":"## More Vagrant info\n\n* [@mitchellh/vagrant](https://github.com/mitchellh/vagrant)\n* [Vagrant Docs](http://docs.vagrantup.com/v1/docs)\n* IRC: [#vagrant on freenode](http://irc.lc/freenode/vagrant)"},{"_id":"2bb26b45b3df0c0870000005","treeId":"2b8eba0efe143b2452000001","seq":1,"position":3,"parentId":"2bb260dcb3df0c0870000002","content":"## Chef-server\n\n* [Open-source tool](http://community.opscode.com/) to manage the state of all your servers that are provisioned by chef-solo.\n* developed by Opscode, who offer a [hosted version](http://www.opscode.com/hosted-chef/)."},{"_id":"2bb274cab3df0c0870000006","treeId":"2b8eba0efe143b2452000001","seq":1,"position":4,"parentId":"2bb260dcb3df0c0870000002","content":"## Chef links\n\n* http://wiki.opscode.com/display/chef/Home\n* http://docs.opscode.com/\n* [#ChefConf 2013](http://chefconf.opscode.com/) April 24-26 SFO\n* [jtimperman's blog](http://jtimberman.housepub.org/) with advanced chef tips\n* IRC: [#chef on freenode](http://irc.lc/freenode/chef)"},{"_id":"2bb2b9fabd6088aefc000004","treeId":"2b8eba0efe143b2452000001","seq":1,"position":5,"parentId":"2bb260dcb3df0c0870000002","content":"## My vagrant/chef contribs\n\n### Drupal and Redmine\n\n* [@dergachev/vagrant_drupal](https://github.com/dergachev/vagrant_drupal) - work in progress\n* [@dergachev/chef-redmine](https://github.com/dergachev/chef_redmine) cookbook\n* [@dergachev/vagrant_redmine](https://github.com/dergachev/vagrant_redmine) tutorial; see extensive [NOTES.md](https://github.com/dergachev/vagrant_redmine/blob/master/NOTES.md)\n\n### User provisioning with agent forwarding\n\n* https://gist.github.com/3866825#vagrant-tutorial\n* LWRP tutorial: [@dergachev/chef\\_extend\\_lwrp]( https://github.com/dergachev/chef_extend_lwrp)\n* [@dergachev/chef\\_root\\_ssh_agent](https://github.com/dergachev/chef_root_ssh_agent) \n* [@dergachev/chef\\_users](https://github.com/dergachev/chef_users)\n* [@dergachev/chef\\_homesick\\_agent](https://github.com/dergachev/chef_homesick_agent), extends [@fnichol/chef-homesick](https://github.com/fnichol/chef-homesick)"}],"tree":{"_id":"2b8eba0efe143b2452000001","name":"Vagrant / Chef","publicUrl":"2b8eba0efe143b2452000001"}}