Permalink
Browse files

Improved module structure.

  • Loading branch information...
1 parent cb20fc9 commit f542fc45e65afafe3ecdf34d68de1363b1c031a2 @cespare cespare committed Mar 27, 2012
View
@@ -1,11 +1,12 @@
-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.
+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
+ include Terraform::Dsl
dep "my library" do
met? { (check if your dependency is met) }
meet { (install your dependency) }
@@ -15,10 +16,11 @@ 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:
+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!
+Dmac -- thanks for the name!
View
@@ -5,6 +5,6 @@ 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)
+ FileUtils.cp(File.expand_path(File.join(File.dirname(__FILE__), "terraform/dsl.rb")), path)
end
end
View
@@ -0,0 +1,155 @@
+# 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 Terraform::Dsl
+# dep "my library" do
+# met? { (check if your dependency is met) }
+# meet { (install your dependency) }
+# end
+
+require "fileutils"
+require "digest/md5"
+
+module Terraform
+ module Dsl
+ 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
+ fail_and_exit "Error: no 'met?' block defined for dep '#{name}'." unless @current_dependency[:met?]
+ fail_and_exit "Error: no 'meet' block defined for dep '#{name}'." unless @current_dependency[:meet]
+ 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: #{package}" do
+ met? { package_installed?(package) }
+ meet { install_package(package) }
+ end
+ end
+
+ # Ensure an Ubuntu PPA is installed. The argument is the ppa location, in the form ppa:[USER]/[NAME]
+ def ensure_ppa(ppa)
+ ppa_part, location = ppa.split(":", 2)
+ fail_and_exit("PPA location must be of the form ppa:[USER]/[NAME]") unless (ppa_part == "ppa") && location
+ ensure_package("python-software-properties")
+ dep "ppa: #{location}" do
+ met? { !`apt-cache policy 2> /dev/null | grep ppa.launchpad.net/#{location}/`.empty? }
+ meet do
+ shell "sudo add-apt-repository #{ppa}"#, :silent => true
+ shell "sudo apt-get update"#, :silent => true
+ end
+ end
+ end
+
+ def gem_installed?(gem) `gem list '#{gem}'`.include?(gem) end
+
+ def ensure_gem(gem)
+ dep "gem: #{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 "file: #{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
+
+ # A task which must be run once to be 'met'. For instance, this might be the DB migration script.
+ def ensure_run_once(name, &block)
+ dep "run task once: #{name}" do
+ has_run_once = false
+ met? { has_run_once }
+ meet do
+ yield
+ has_run_once = true
+ 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
+end
@@ -1,153 +0,0 @@
-# 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
- fail_and_exit "Error: no 'met?' block defined for dep '#{name}'." unless @current_dependency[:met?]
- fail_and_exit "Error: no 'meet' block defined for dep '#{name}'." unless @current_dependency[:meet]
- 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: #{package}" do
- met? { package_installed?(package) }
- meet { install_package(package) }
- end
- end
-
- # Ensure an Ubuntu PPA is installed. The argument is the ppa location, in the form ppa:[USER]/[NAME]
- def ensure_ppa(ppa)
- ppa_part, location = ppa.split(":", 2)
- fail_and_exit("PPA location must be of the form ppa:[USER]/[NAME]") unless (ppa_part == "ppa") && location
- ensure_package("python-software-properties")
- dep "ppa: #{location}" do
- met? { !`apt-cache policy 2> /dev/null | grep ppa.launchpad.net/#{location}/`.empty? }
- meet do
- shell "sudo add-apt-repository #{ppa}"#, :silent => true
- shell "sudo apt-get update"#, :silent => true
- end
- end
- end
-
- def gem_installed?(gem) `gem list '#{gem}'`.include?(gem) end
-
- def ensure_gem(gem)
- dep "gem: #{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 "file: #{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
-
- # A task which must be run once to be 'met'. For instance, this might be the DB migration script.
- def ensure_run_once(name, &block)
- dep "run task once: #{name}" do
- has_run_once = false
- met? { has_run_once }
- meet do
- yield
- has_run_once = true
- 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
@@ -1,8 +1,8 @@
require File.expand_path(File.join(File.dirname(__FILE__), "../../test_helper.rb"))
-require "terraform/terraform_dsl"
+require "terraform/dsl"
class DslTest < Scope::TestCase
- include TerraformDsl
+ include Terraform::Dsl
class ProcessExit < StandardError; end

0 comments on commit f542fc4

Please sign in to comment.