Skip to content

Instantly share code, notes, and snippets.

@fnichol
Created December 28, 2011 17:41
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save fnichol/1528832 to your computer and use it in GitHub Desktop.
Save fnichol/1528832 to your computer and use it in GitHub Desktop.
Vagrantify: Create a chef-solo enabled Vagrant virtual machine for development

Installation

gem install thor
mkdir -p ~/vagrants && cd ~/vagrants
curl -LO https://raw.github.com/gist/1528832/vagrantify
chmod 755 vagrantify

Usage

./vagrantify init webserver

Creates a Vagrant VM project that looks like:

  webserver
  ├── Gemfile
  ├── Gemfile.lock
  ├── Vagrantfile
  └── chef-repo
      ├── Cheffile
      ├── Cheffile.lock
      ├── cookbooks
      │   ├── solo_data_bags
      │   │   ├── CHANGELOG.md
      │   │   ├── README.md
      │   │   ├── libraries
      │   │   ├── metadata.json
      │   │   ├── metadata.rb
      │   │   └── recipes
      │   └── user
      │       ├── CHANGELOG.md
      │       ├── README.md
      │       ├── attributes
      │       ├── metadata.json
      │       ├── metadata.rb
      │       ├── providers
      │       ├── recipes
      │       ├── resources
      │       └── templates
      ├── data_bags
      │   └── users
      │       └── fnichol.json
      └── roles

Don't need Chef? Ok:

./vagrantify init webserver --no-chef

Full usage options:

./vagrantify help init
Usage:
  vagrantify init VM_NAME [options]

Options:
  [--bundler]  # Creates Gemfile and runs bundle install
               # Default: true
  [--git]      # Creates a .gitignore file and runs git init
               # Default: true
  [--chef]     # Creates a chef-repo suitable for Chef cookbook development
               # Default: true
  [--box]      # Set the default box

Initializes a Vagrant VM Project
#!/usr/bin/env ruby
require 'rubygems'
require 'thor'
class Vagrantify < Thor
include Thor::Actions
desc "init VM_NAME [options]", "Initializes a Vagrant VM Project"
method_option :bundler, :type => :boolean, :default => true,
:desc => "Creates Gemfile and runs bundle install"
method_option :git, :type => :boolean, :default => true,
:desc => "Creates a .gitignore file and runs git init"
method_option :chef, :type => :boolean, :default => true,
:desc => "Creates a chef-repo suitable for Chef cookbook development"
method_option :box, :type => :string,
:desc => 'Default box name'
def init(name)
@vm_name = name
@chef_repo = options[:chef] ? "#{vm_name}/chef-repo" : nil
empty_directory vm_name
install_vagrant_butter
bundlerize if options[:bundler]
git_init if options[:git]
create_chef_repo if options[:chef]
create_vagrantfile(options[:box])
end
private
attr_reader :vm_name
attr_reader :chef_repo
def bundlerize
create_gemfile
install_bundler
inside vm_name do
run "bundle install"
end
end
def create_gemfile
create_file "#{vm_name}/Gemfile", <<-GEMFILE.gsub(/^ {6}/, '')
source "http://rubygems.org"
gem 'librarian'
GEMFILE
end
def install_bundler
run "gem install bundler --pre" unless %x[gem list --no-versions] =~ /^bundler$/
end
def install_vagrant_butter
unless %x[vagrant gem list --no-versions] =~ /^vagrant-butter$/
run "vagrant gem install vagrant-butter"
end
end
def create_chef_repo
empty_directory chef_repo
%w{.chef cookbooks data_bags roles tmp}.each do |dir|
empty_directory "#{chef_repo}/#{dir}"
end
create_rvmrc
create_knife_rb
create_cheffile
create_user_json
inside chef_repo do
run "librarian-chef install --clean"
end
end
def create_rvmrc
create_file "#{chef_repo}/.rvmrc", <<-RVMRC.gsub(/^ {6}/, '')
#!/usr/bin/env bash
# This is an RVM Project .rvmrc file, used to automatically load the ruby
# development environment upon cd'ing into the directory
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
environment_id="default"
#
# Uncomment following line if you want options to be set only for given project.
#
# PROJECT_JRUBY_OPTS=( --1.9 )
#
# First we attempt to load the desired environment directly from the environment
# file. This is very fast and efficient compared to running through the entire
# CLI and selector. If you want feedback on which environment was used then
# insert the word 'use' after --create as this triggers verbose mode.
#
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
then
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
then
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
fi
else
# If the environment file has not yet been created, use the RVM CLI to select.
if ! rvm --create "$environment_id"
then
echo "Failed to create RVM environment '${environment_id}'."
exit 1
fi
fi
for f in ${HOME}/.chef.d/env-opscode.sh ${HOME}/.aws/env.sh ; do
if [[ -f "${f}" ]] ; then
source "$f"
fi
done ; unset f
RVMRC
end
def create_cheffile
create_file "#{chef_repo}/Cheffile", <<-CHEFFILE.gsub(/^ {6}/, '')
#!/usr/bin/env ruby
site 'http://community.opscode.com/api/v1'
cookbook 'solo_data_bags'
cookbook 'user'
CHEFFILE
end
def create_knife_rb
create_file "#{chef_repo}/.chef/knife.rb", <<-'KNIFE_RB'.gsub(/^ {6}/, '')
### Chef Solo
#
#
### Opscode Hosted Chef Server
#
# export KNIFE_USER="jdoe"
# export KNIFE_ORGNAME="acmeco"
#
# * Your Opscode client key should be at `~/.chef.d/opscode-jdoe.pem`.
# * Your Opscode validation key should be at `~/.chef.d/opscode-acmeco-validator.pem`.
#
### Chef Server
#
# export KNIFE_USER="hsolo"
# export KNIFE_SERVER_NAME="widgetinc"
# export KNIFE_SERVER_URL="https://chef.widgetinc.com"
#
# * Your Chef Server client key should be at `~/.chef.d/widgetinc-hsolo.pem`.
# * Your Chef Server validation key should be at `~/.chef.d/widgetinc-validator.pem`.
#
current_dir = File.dirname(__FILE__)
home_dir = ENV['HOME']
chef_dir = "#{home_dir}/.chef.d"
user = ENV['KNIFE_USER'] || ENV['USER']
orgname = ENV['KNIFE_ORGNAME']
server_name = ENV['KNIFE_SERVER_NAME']
server_url = ENV['KNIFE_SERVER_URL']
# path to cookbooks
cookbook_path ["#{current_dir}/../cookbooks",
"#{current_dir}/../site-cookbooks"]
# logging details
log_level :info
log_location STDOUT
# user/client and private key to authenticate to a Chef Server, if needed
node_name user
if orgname
# if KNIFE_ORGNAME is given, then we're talking to the Opscode Hosted Chef
# Server
validation_client_name "#{orgname}-validator"
client_key "#{chef_dir}/opscode-#{user}.pem"
validation_key "#{chef_dir}/opscode-#{orgname}-validator.pem"
chef_server_url "https://api.opscode.com/organizations/#{orgname}"
elsif server_name
# if KNIFE_SERVER_NAME is defined, then we're talking to a Chef Server
validation_client_name "#{server_name}-validator"
client_key "#{chef_dir}/#{server_name}-#{user}.pem"
validation_key "#{chef_dir}/#{server_name}-validator.pem"
chef_server_url server_url
end
# caching options
cache_type 'BasicFile'
cache_options( :path => "#{home_dir}/.chef/checksums" )
# new cookbook defaults
cookbook_copyright ENV['KNIFE_COOKBOOK_COPYRIGHT'] ||
%x{git config --get user.name}.chomp
cookbook_email ENV['KNIFE_COOKBOOK_EMAIL'] ||
%x{git config --get user.email}.chomp
cookbook_license "apachev2"
KNIFE_RB
end
def create_user_json
json_file = "#{chef_repo}/data_bags/users/#{ENV['USER']}.json"
empty_directory File.dirname(json_file)
create_file json_file, %[{ "id": "#{ENV['USER']}" }]
end
def create_vagrantfile(default_box = nil)
create_file "#{vm_name}/Vagrantfile", <<-VAGRANTFILE.gsub(/^ {6}/, '')
#!/usr/bin/env ruby
# coding: utf-8
include Vagrant::Butter::Helpers
Vagrant::Config.run do |config|
#{vm_boxes(default_box)}
# config.vm.customize ["modifyvm", :id, "--memory", "256"]
#{chef_solo_provisioner || ''}
end
VAGRANTFILE
end
def vm_boxes(default_box = nil)
other_boxes = %x[vagrant box list].split("\n").sort
default_box ||= other_boxes.shift
boxes = Array(%{ config.vm.box = "#{default_box}"})
other_boxes.each { |box| boxes << %{ # config.vm.box = "#{box}"} }
boxes.join("\n")
end
def chef_solo_provisioner
if options[:chef]
<<-PROVISION.gsub(/^ {6}/, '')
config.vm.provision :chef_solo do |chef|
chef.cookbooks_path = "chef-repo/cookbooks"
chef.roles_path = "chef-repo/roles"
chef.data_bags_path = "chef-repo/data_bags"
chef.log_level = :debug if ENV['vdb']
# patches
chef.add_recipe "solo_data_bags"
# LWRPs
chef.add_recipe "user"
# recipes
# chef.add_recipe "apt::cacher-client"
# chef.add_recipe "apt"
# chef.add_recipe "user::data_bag"
chef.json = {
# 'apt' => {
# 'cacher_ipaddress' => local_ip
# },
# 'users' => [
# '#{ENV['USER']}'
# ]
}
end
PROVISION
end
end
def git_init
create_file "#{vm_name}/.gitignore", <<-GITIGNORE.gsub(/^ {6}/, '')
/.vagrant
/Gemfile.lock
/chef-repo/cookbooks/
/chef-repo/tmp/
GITIGNORE
inside vm_name do
run "git init" unless File.directory? ".git"
end
end
end
Vagrantify.start
@calavera
Copy link

this is really useful @fnichol, thank you. I'm kind of lazy so I added a new option to allow set the default box directly, check it out here:

https://gist.github.com/1934008

@fnichol
Copy link
Author

fnichol commented Feb 28, 2012

@calavera Sweet, thanks! So glad these are git repos underneath--"pull request" merged :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment