Skip to content

Commit

Permalink
Terraform arrives
Browse files Browse the repository at this point in the history
  • Loading branch information
philc committed Mar 24, 2012
0 parents commit 513c6f9
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
*.gem
.bundle
Gemfile.lock
pkg/*
4 changes: 4 additions & 0 deletions Gemfile
@@ -0,0 +1,4 @@
source "http://rubygems.org"

# Specify your gem's dependencies in terraform.gemspec
gemspec
24 changes: 24 additions & 0 deletions README.markdown
@@ -0,0 +1,24 @@
Terraform is a small goal-oriented DSL for setting up a machine, similar in purpose to Chef and
Puppet. Its design is inspired by Babushka, but it's simpler and tailored specifically for provisioning a machine for a webapp.

Usage
-----

require "terraform_dsl"
include TerraformDsl
dep "my library" do
met? { (check if your dependency is met) }
meet { (install your dependency) }
end

A more detailed README is coming shortly.

Contribute
----------
When editing this gem, to test your changes, you can load your local copy of the gem in your project by using this in your Gemfile:

gem "terraform", :path => "~/p/terraform"

Credits
-------
Dmac -- thanks for the name!
1 change: 1 addition & 0 deletions Rakefile
@@ -0,0 +1 @@
require "bundler/gem_tasks"
10 changes: 10 additions & 0 deletions lib/terraform.rb
@@ -0,0 +1,10 @@
require "terraform/version"
require "fileutils"

module Terraform
# Writes the terraform_dsl.rb to the given file or directory.
def self.write_dsl_file(path)
path = File.join(path, "terraform_dsl.rb") if File.directory?(path)
FileUtils.cp(File.expand_path(File.join(File.dirname(__FILE__), "terraform/terraform_dsl.rb")), path)
end
end
125 changes: 125 additions & 0 deletions lib/terraform/terraform_dsl.rb
@@ -0,0 +1,125 @@
# This is small goal-oriented DSL for installing system components, similar in purpose to Chef and Puppet.
# Its design is inspired by Babushka but it's simpler and tailored specifically for provisioning a machine
# for a webapp.
#
# Usage:
#
# require "terraform_dsl"
# include TerraformDsl
# dep "my library" do
# met? { (check if your dependency is met) }
# meet { (install your dependency) }
# end

require "fileutils"
require "digest/md5"

module TerraformDsl
def dep(name)
@dependencies ||= []
# If a dep gets required or defined twice, only run it once.
return if @dependencies.find { |dep| dep[:name] == name }
@dependencies.push(@current_dependency = { :name => name })
yield
end
def met?(&block) @current_dependency[:met?] = block end
def meet(&block) @current_dependency[:meet] = block end
def in_path?(command) `which #{command}`.size > 0 end
def fail_and_exit(message) puts message; exit 1 end

# Runs a command and raises an exception if its exit status was nonzero.
# - silent: if false, log the command being run and its stdout. False by default.
# - check_exit_code: raises an error if the command had a non-zero exit code. True by default.
def shell(command, options = {})
silent = (options[:silent] != false)
puts command unless silent
output = `#{command}`
puts output unless output.empty? || silent
raise "#{command} had a failure exit status of #{$?.to_i}" unless $?.to_i == 0
true
end

def satisfy_dependencies
STDOUT.sync = true # Ensure that we flush logging output as we go along.
@dependencies.each do |dep|
unless dep[:met?].call
puts "* Dependency #{dep[:name]} is not met. Meeting it."
dep[:meet].call
fail_and_exit %Q("met?" for #{dep[:name]} is still false after running "meet".) unless dep[:met?].call
end
end
end

#
# These are very common tasks which are needed by almost everyone, and so they're bundled with this DSL.
#

def package_installed?(package) `dpkg -s #{package} 2> /dev/null | grep Status`.match(/\sinstalled/) end
def install_package(package)
# Specify a noninteractive frontend, so dpkg won't prompt you for info. -q is quiet; -y is "answer yes".
shell "sudo DEBIAN_FRONTEND=noninteractive apt-get install -qy #{package}"
end

def ensure_packages(*packages) packages.each { |package| ensure_package(package) } end
def ensure_package(package)
dep package do
met? { package_installed?(package) }
meet { install_package(package) }
end
end

def gem_installed?(gem) `gem list '#{gem}'`.include?(gem) end

def ensure_gem(gem)
dep gem do
met? { gem_installed?(gem) }
meet { shell "gem install #{gem} --no-ri --no-rdoc" }
end
end

# Ensures the file at dest_path is exactly the same as the one in source_path.
# Invokes the given block if the file is changed. Use this block to restart a service, for instance.
def ensure_file(source_path, dest_path, &on_change)
dep dest_path do
met? do
raise "This file does not exist: #{source_path}" unless File.exists?(source_path)
File.exists?(dest_path) && (Digest::MD5.file(source_path) == Digest::MD5.file(dest_path))
end
meet do
FileUtils.cp(source_path, dest_path)
on_change.call if on_change
end
end
end

def ensure_rbenv
ensure_package "git-core"
dep "rbenv" do
met? { in_path?("rbenv") }
meet do
# These instructions are from https://github.com/sstephenson/rbenv/wiki/Using-rbenv-in-Production
shell "wget -q -O - https://raw.github.com/fesplugas/rbenv-installer/master/bin/rbenv-installer | bash"
# We need to run rbenv init after install, which adjusts the path. If exec is causing us problems
# down the road, we can perhaps simulate running rbenv init without execing.
unless ARGV.include?("--forked-after-rbenv") # To guard against an infinite forking loop.
exec "bash -c 'source ~/.bashrc; #{$0} --forked-after-rbenv'" # $0 is the current process's name.
end
end
end
end

# ruby_version is a rbenv ruby version string like "1.9.2-p290".
def ensure_rbenv_ruby(ruby_version)
ensure_rbenv
ensure_packages "curl", "build-essential", "libxslt1-dev", "libxml2-dev", "libssl-dev"

dep "rbenv ruby #{ruby_version}" do
met? { `which ruby`.include?("rbenv") && `ruby -v`.include?(ruby_version.gsub("-", "")) }
meet do
puts "Compiling Ruby will take a few minutes."
shell "rbenv install #{ruby_version}"
shell "rbenv rehash"
end
end
end
end
3 changes: 3 additions & 0 deletions lib/terraform/version.rb
@@ -0,0 +1,3 @@
module Terraform
VERSION = "0.0.1"
end
20 changes: 20 additions & 0 deletions terraform.gemspec
@@ -0,0 +1,20 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "terraform/version"

Gem::Specification.new do |s|
s.name = "terraform"
s.version = Terraform::VERSION
s.authors = ["Phil Crosby"]
s.email = ["phil.crosby@gmail.com"]
s.homepage = "http://github.com/philc/terraform"
s.summary = %q{Set up a cold, inhospitable system using Terraform.}
# s.description = %q{TODO: Write a gem description}

s.rubyforge_project = "terraform"

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
end

0 comments on commit 513c6f9

Please sign in to comment.