terraform 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,12 @@
1
- Terraform is a small goal-oriented DSL for setting up a machine, similar in purpose to Chef and
2
- Puppet. Its design is inspired by Babushka, but it's simpler and tailored specifically for provisioning a machine for a webapp.
1
+ Terraform is a small goal-oriented DSL for setting up a machine, similar in purpose to Chef and Puppet. Its
2
+ design is inspired by Babushka, but it's simpler and tailored specifically for provisioning a machine for a
3
+ webapp.
3
4
 
4
5
  Usage
5
6
  -----
6
7
 
7
8
  require "terraform_dsl"
8
- include TerraformDsl
9
+ include Terraform::Dsl
9
10
  dep "my library" do
10
11
  met? { (check if your dependency is met) }
11
12
  meet { (install your dependency) }
@@ -15,10 +16,11 @@ A more detailed README is coming shortly.
15
16
 
16
17
  Contribute
17
18
  ----------
18
- 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:
19
+ When editing this gem, to test your changes, you can load your local copy of the gem in your project by using
20
+ this in your Gemfile:
19
21
 
20
22
  gem "terraform", :path => "~/p/terraform"
21
23
 
22
24
  Credits
23
25
  -------
24
- Dmac -- thanks for the name!
26
+ Dmac -- thanks for the name!
data/Rakefile CHANGED
@@ -1 +1,11 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :test => ["test:units"]
5
+
6
+ namespace :test do
7
+ Rake::TestTask.new(:units) do |task|
8
+ task.libs << "test"
9
+ task.test_files = FileList["test/unit/**/*_test.rb"]
10
+ end
11
+ end
@@ -5,6 +5,6 @@ module Terraform
5
5
  # Writes the terraform_dsl.rb to the given file or directory.
6
6
  def self.write_dsl_file(path)
7
7
  path = File.join(path, "terraform_dsl.rb") if File.directory?(path)
8
- FileUtils.cp(File.expand_path(File.join(File.dirname(__FILE__), "terraform/terraform_dsl.rb")), path)
8
+ FileUtils.cp(File.expand_path(File.join(File.dirname(__FILE__), "terraform/dsl.rb")), path)
9
9
  end
10
10
  end
@@ -0,0 +1,144 @@
1
+ require "fileutils"
2
+ require "digest/md5"
3
+
4
+ module Terraform
5
+ module Dsl
6
+ def dep(name)
7
+ @dependencies ||= []
8
+ # If a dep gets required or defined twice, only run it once.
9
+ return if @dependencies.find { |dep| dep[:name] == name }
10
+ @dependencies.push(@current_dependency = { :name => name })
11
+ yield
12
+ fail_and_exit "Error: no 'met?' block defined for dep '#{name}'." unless @current_dependency[:met?]
13
+ fail_and_exit "Error: no 'meet' block defined for dep '#{name}'." unless @current_dependency[:meet]
14
+ end
15
+ def met?(&block) @current_dependency[:met?] = block end
16
+ def meet(&block) @current_dependency[:meet] = block end
17
+ def in_path?(command) `which #{command}`.size > 0 end
18
+ def fail_and_exit(message) puts message; exit 1 end
19
+
20
+ # Runs a command and raises an exception if its exit status was nonzero.
21
+ # - silent: if false, log the command being run and its stdout. False by default.
22
+ # - check_exit_code: raises an error if the command had a non-zero exit code. True by default.
23
+ def shell(command, options = {})
24
+ silent = (options[:silent] != false)
25
+ puts command unless silent
26
+ output = `#{command}`
27
+ puts output unless output.empty? || silent
28
+ raise "#{command} had a failure exit status of #{$?.to_i}" unless $?.to_i == 0
29
+ true
30
+ end
31
+
32
+ def satisfy_dependencies
33
+ STDOUT.sync = true # Ensure that we flush logging output as we go along.
34
+ @dependencies.each do |dep|
35
+ unless dep[:met?].call
36
+ puts "* Dependency #{dep[:name]} is not met. Meeting it."
37
+ dep[:meet].call
38
+ fail_and_exit %Q("met?" for #{dep[:name]} is still false after running "meet".) unless dep[:met?].call
39
+ end
40
+ end
41
+ end
42
+
43
+ #
44
+ # These are very common tasks which are needed by almost everyone, and so they're bundled with this DSL.
45
+ #
46
+
47
+ def package_installed?(package) `dpkg -s #{package} 2> /dev/null | grep Status`.match(/\sinstalled/) end
48
+ def install_package(package)
49
+ # Specify a noninteractive frontend, so dpkg won't prompt you for info. -q is quiet; -y is "answer yes".
50
+ shell "sudo DEBIAN_FRONTEND=noninteractive apt-get install -qy #{package}"
51
+ end
52
+
53
+ def ensure_packages(*packages) packages.each { |package| ensure_package(package) } end
54
+ def ensure_package(package)
55
+ dep "package: #{package}" do
56
+ met? { package_installed?(package) }
57
+ meet { install_package(package) }
58
+ end
59
+ end
60
+
61
+ # Ensure an Ubuntu PPA is installed. The argument is the ppa location, in the form ppa:[USER]/[NAME]
62
+ def ensure_ppa(ppa)
63
+ ppa_part, location = ppa.split(":", 2)
64
+ fail_and_exit("PPA location must be of the form ppa:[USER]/[NAME]") unless (ppa_part == "ppa") && location
65
+ # The python-software-properties package provides the add-apt-repository convenience tool for editing
66
+ # the list of apt's source repositories.
67
+ ensure_package("python-software-properties")
68
+ dep "ppa: #{location}" do
69
+ met? { !`apt-cache policy 2> /dev/null | grep ppa.launchpad.net/#{location}/`.empty? }
70
+ meet do
71
+ shell "sudo add-apt-repository #{ppa}"#, :silent => true
72
+ shell "sudo apt-get update"#, :silent => true
73
+ end
74
+ end
75
+ end
76
+
77
+ def gem_installed?(gem) `gem list '#{gem}'`.include?(gem) end
78
+
79
+ def ensure_gem(gem)
80
+ dep "gem: #{gem}" do
81
+ met? { gem_installed?(gem) }
82
+ meet { shell "gem install #{gem} --no-ri --no-rdoc" }
83
+ end
84
+ end
85
+
86
+ # Ensures the file at dest_path is exactly the same as the one in source_path.
87
+ # Invokes the given block if the file is changed. Use this block to restart a service, for instance.
88
+ def ensure_file(source_path, dest_path, &on_change)
89
+ dep "file: #{dest_path}" do
90
+ met? do
91
+ raise "This file does not exist: #{source_path}" unless File.exists?(source_path)
92
+ File.exists?(dest_path) && (Digest::MD5.file(source_path) == Digest::MD5.file(dest_path))
93
+ end
94
+ meet do
95
+ FileUtils.cp(source_path, dest_path)
96
+ on_change.call if on_change
97
+ end
98
+ end
99
+ end
100
+
101
+ # A task which must be run once to be 'met'. For instance, this might be the DB migration script.
102
+ def ensure_run_once(name, &block)
103
+ dep "run task once: #{name}" do
104
+ has_run_once = false
105
+ met? { has_run_once }
106
+ meet do
107
+ yield
108
+ has_run_once = true
109
+ end
110
+ end
111
+ end
112
+
113
+ def ensure_rbenv
114
+ ensure_package "git-core"
115
+ dep "rbenv" do
116
+ met? { in_path?("rbenv") }
117
+ meet do
118
+ # These instructions are from https://github.com/sstephenson/rbenv/wiki/Using-rbenv-in-Production
119
+ shell "wget -q -O - https://raw.github.com/fesplugas/rbenv-installer/master/bin/rbenv-installer | bash"
120
+ # We need to run rbenv init after install, which adjusts the path. If exec is causing us problems
121
+ # down the road, we can perhaps simulate running rbenv init without execing.
122
+ unless ARGV.include?("--forked-after-rbenv") # To guard against an infinite forking loop.
123
+ exec "bash -c 'source ~/.bashrc; #{$0} --forked-after-rbenv'" # $0 is the current process's name.
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ # ruby_version is a rbenv ruby version string like "1.9.2-p290".
130
+ def ensure_rbenv_ruby(ruby_version)
131
+ ensure_rbenv
132
+ ensure_packages "curl", "build-essential", "libxslt1-dev", "libxml2-dev", "libssl-dev"
133
+
134
+ dep "rbenv ruby: #{ruby_version}" do
135
+ met? { `which ruby`.include?("rbenv") && `ruby -v`.include?(ruby_version.gsub("-", "")) }
136
+ meet do
137
+ puts "Compiling Ruby will take a few minutes."
138
+ shell "rbenv install #{ruby_version}"
139
+ shell "rbenv rehash"
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -1,3 +1,3 @@
1
1
  module Terraform
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -15,6 +15,9 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "scope", "~> 0.2.3"
22
+ s.add_development_dependency "rr", "~> 1.0.4"
20
23
  end
@@ -0,0 +1,24 @@
1
+ require "bundler/setup"
2
+ require "minitest/autorun"
3
+ require "scope"
4
+ require "rr"
5
+ require "stringio"
6
+
7
+ $:.unshift(File.join(File.dirname(__FILE__), "../lib"))
8
+
9
+ module Scope
10
+ class TestCase
11
+ include RR::Adapters::MiniTest
12
+ end
13
+ end
14
+
15
+ module Kernel
16
+ def capture_output
17
+ result = StringIO.new
18
+ $stdout = result
19
+ yield
20
+ result.string
21
+ ensure
22
+ $stdout = STDOUT
23
+ end
24
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "../../test_helper.rb"))
2
+ require "terraform/dsl"
3
+
4
+ class DslTest < Scope::TestCase
5
+ include Terraform::Dsl
6
+
7
+ class ProcessExit < StandardError; end
8
+
9
+ # Methods to mock for met? and meet blocks
10
+ def do_met() end
11
+ def do_meet() end
12
+
13
+ setup do
14
+ stub(self).fail_and_exit(anything) { raise ProcessExit }
15
+ end
16
+
17
+ context "dep declarations" do
18
+ should "fail if there is no met? or meet given" do
19
+ assert_raises(ProcessExit) do
20
+ dep("no met?") { meet {} }
21
+ end
22
+ assert_raises(ProcessExit) do
23
+ dep("no meet") { met? {} }
24
+ end
25
+ end
26
+
27
+ should "not run meet if met? is satisfied" do
28
+ mock(self).do_met { true }
29
+ mock(self).do_meet.never
30
+ dep "foo" do
31
+ met? { do_met }
32
+ meet { do_meet }
33
+ end
34
+ satisfy_dependencies
35
+ end
36
+
37
+ should "run meet if met? is not satisfied" do
38
+ met_run = false
39
+ mock(self).do_met.twice { result = met_run; met_run = true; result }
40
+ mock(self).do_meet
41
+ dep "foo" do
42
+ met? { do_met }
43
+ meet { do_meet }
44
+ end
45
+ message = capture_output { satisfy_dependencies }
46
+ assert_match /Dependency foo is not met/, message
47
+ end
48
+
49
+ should "only meet a dep once" do
50
+ met_run = false
51
+ mock(self).do_met.twice { result = met_run; met_run = true; result }
52
+ mock(self).do_meet
53
+ 2.times do
54
+ dep "foo" do
55
+ met? { do_met }
56
+ meet { do_meet }
57
+ end
58
+ end
59
+ capture_output { satisfy_dependencies }
60
+ end
61
+
62
+ should "fail if met? still fails after running meet" do
63
+ mock(self).do_met.twice { false }
64
+ mock(self).do_meet
65
+ dep "foo" do
66
+ met? { do_met }
67
+ meet { do_meet }
68
+ end
69
+ assert_raises(ProcessExit) { capture_output { satisfy_dependencies } }
70
+ end
71
+ end
72
+
73
+ context "ensure_ppa" do
74
+ should "fail if the PPA name is not the expected form" do
75
+ assert_raises(ProcessExit) { ensure_ppa("blah") }
76
+ assert_raises(ProcessExit) { ensure_ppa("http://some.ppa.url") }
77
+ end
78
+
79
+ # TODO(caleb): Add more unit tests. Having trouble coming up with unit tests that don't feel
80
+ # fragile/artificial. For helpers like these, integration tests of some kind that run on a vagrant box or
81
+ # something may prove more useful.
82
+ end
83
+
84
+ context "ensure_run_once" do
85
+ should "run a task exactly once" do
86
+ mock(self).do_meet.never
87
+ meet_run = false
88
+ ensure_run_once("foo") { meet_run = true }
89
+ dep "run task once: foo" do
90
+ met? { do_met }
91
+ meet { do_meet }
92
+ end
93
+ capture_output { satisfy_dependencies }
94
+ assert meet_run
95
+ end
96
+ end
97
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terraform
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Phil Crosby
@@ -15,10 +15,41 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-03-24 00:00:00 -07:00
18
+ date: 2012-03-27 00:00:00 -07:00
19
19
  default_executable:
20
- dependencies: []
21
-
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: scope
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 17
30
+ segments:
31
+ - 0
32
+ - 2
33
+ - 3
34
+ version: 0.2.3
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rr
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 31
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 4
50
+ version: 1.0.4
51
+ type: :development
52
+ version_requirements: *id002
22
53
  description:
23
54
  email:
24
55
  - phil.crosby@gmail.com
@@ -34,9 +65,11 @@ files:
34
65
  - README.markdown
35
66
  - Rakefile
36
67
  - lib/terraform.rb
37
- - lib/terraform/terraform_dsl.rb
68
+ - lib/terraform/dsl.rb
38
69
  - lib/terraform/version.rb
39
70
  - terraform.gemspec
71
+ - test/test_helper.rb
72
+ - test/unit/terraform/dsl_test.rb
40
73
  has_rdoc: true
41
74
  homepage: http://github.com/philc/terraform
42
75
  licenses: []
@@ -71,5 +104,6 @@ rubygems_version: 1.6.2
71
104
  signing_key:
72
105
  specification_version: 3
73
106
  summary: Set up a cold, inhospitable system using Terraform.
74
- test_files: []
75
-
107
+ test_files:
108
+ - test/test_helper.rb
109
+ - test/unit/terraform/dsl_test.rb
@@ -1,125 +0,0 @@
1
- # This is small goal-oriented DSL for installing system components, similar in purpose to Chef and Puppet.
2
- # Its design is inspired by Babushka but it's simpler and tailored specifically for provisioning a machine
3
- # for a webapp.
4
- #
5
- # Usage:
6
- #
7
- # require "terraform_dsl"
8
- # include TerraformDsl
9
- # dep "my library" do
10
- # met? { (check if your dependency is met) }
11
- # meet { (install your dependency) }
12
- # end
13
-
14
- require "fileutils"
15
- require "digest/md5"
16
-
17
- module TerraformDsl
18
- def dep(name)
19
- @dependencies ||= []
20
- # If a dep gets required or defined twice, only run it once.
21
- return if @dependencies.find { |dep| dep[:name] == name }
22
- @dependencies.push(@current_dependency = { :name => name })
23
- yield
24
- end
25
- def met?(&block) @current_dependency[:met?] = block end
26
- def meet(&block) @current_dependency[:meet] = block end
27
- def in_path?(command) `which #{command}`.size > 0 end
28
- def fail_and_exit(message) puts message; exit 1 end
29
-
30
- # Runs a command and raises an exception if its exit status was nonzero.
31
- # - silent: if false, log the command being run and its stdout. False by default.
32
- # - check_exit_code: raises an error if the command had a non-zero exit code. True by default.
33
- def shell(command, options = {})
34
- silent = (options[:silent] != false)
35
- puts command unless silent
36
- output = `#{command}`
37
- puts output unless output.empty? || silent
38
- raise "#{command} had a failure exit status of #{$?.to_i}" unless $?.to_i == 0
39
- true
40
- end
41
-
42
- def satisfy_dependencies
43
- STDOUT.sync = true # Ensure that we flush logging output as we go along.
44
- @dependencies.each do |dep|
45
- unless dep[:met?].call
46
- puts "* Dependency #{dep[:name]} is not met. Meeting it."
47
- dep[:meet].call
48
- fail_and_exit %Q("met?" for #{dep[:name]} is still false after running "meet".) unless dep[:met?].call
49
- end
50
- end
51
- end
52
-
53
- #
54
- # These are very common tasks which are needed by almost everyone, and so they're bundled with this DSL.
55
- #
56
-
57
- def package_installed?(package) `dpkg -s #{package} 2> /dev/null | grep Status`.match(/\sinstalled/) end
58
- def install_package(package)
59
- # Specify a noninteractive frontend, so dpkg won't prompt you for info. -q is quiet; -y is "answer yes".
60
- shell "sudo DEBIAN_FRONTEND=noninteractive apt-get install -qy #{package}"
61
- end
62
-
63
- def ensure_packages(*packages) packages.each { |package| ensure_package(package) } end
64
- def ensure_package(package)
65
- dep package do
66
- met? { package_installed?(package) }
67
- meet { install_package(package) }
68
- end
69
- end
70
-
71
- def gem_installed?(gem) `gem list '#{gem}'`.include?(gem) end
72
-
73
- def ensure_gem(gem)
74
- dep gem do
75
- met? { gem_installed?(gem) }
76
- meet { shell "gem install #{gem} --no-ri --no-rdoc" }
77
- end
78
- end
79
-
80
- # Ensures the file at dest_path is exactly the same as the one in source_path.
81
- # Invokes the given block if the file is changed. Use this block to restart a service, for instance.
82
- def ensure_file(source_path, dest_path, &on_change)
83
- dep dest_path do
84
- met? do
85
- raise "This file does not exist: #{source_path}" unless File.exists?(source_path)
86
- File.exists?(dest_path) && (Digest::MD5.file(source_path) == Digest::MD5.file(dest_path))
87
- end
88
- meet do
89
- FileUtils.cp(source_path, dest_path)
90
- on_change.call if on_change
91
- end
92
- end
93
- end
94
-
95
- def ensure_rbenv
96
- ensure_package "git-core"
97
- dep "rbenv" do
98
- met? { in_path?("rbenv") }
99
- meet do
100
- # These instructions are from https://github.com/sstephenson/rbenv/wiki/Using-rbenv-in-Production
101
- shell "wget -q -O - https://raw.github.com/fesplugas/rbenv-installer/master/bin/rbenv-installer | bash"
102
- # We need to run rbenv init after install, which adjusts the path. If exec is causing us problems
103
- # down the road, we can perhaps simulate running rbenv init without execing.
104
- unless ARGV.include?("--forked-after-rbenv") # To guard against an infinite forking loop.
105
- exec "bash -c 'source ~/.bashrc; #{$0} --forked-after-rbenv'" # $0 is the current process's name.
106
- end
107
- end
108
- end
109
- end
110
-
111
- # ruby_version is a rbenv ruby version string like "1.9.2-p290".
112
- def ensure_rbenv_ruby(ruby_version)
113
- ensure_rbenv
114
- ensure_packages "curl", "build-essential", "libxslt1-dev", "libxml2-dev", "libssl-dev"
115
-
116
- dep "rbenv ruby #{ruby_version}" do
117
- met? { `which ruby`.include?("rbenv") && `ruby -v`.include?(ruby_version.gsub("-", "")) }
118
- meet do
119
- puts "Compiling Ruby will take a few minutes."
120
- shell "rbenv install #{ruby_version}"
121
- shell "rbenv rehash"
122
- end
123
- end
124
- end
125
- end