Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Terraform arrives

  • Loading branch information...
commit 513c6f93e90d3cb167bf1c6d1db86ebdca39670e 0 parents
Phil Crosby authored
4 .gitignore
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in terraform.gemspec
+gemspec
24 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  Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
10 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 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  lib/terraform/version.rb
@@ -0,0 +1,3 @@
+module Terraform
+ VERSION = "0.0.1"
+end
20 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
Please sign in to comment.
Something went wrong with that request. Please try again.