michaeldwan-divvy 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Michael Dwan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ = Divvy
2
+
3
+ Divvy is an easy to configure, easy to extend server provisioning framework written in Ruby.
4
+
5
+ I created this for use on a specific project and have not yet gotten around to documenting or testing it :)
6
+
7
+ Stay tuned.
8
+
9
+ == Principals
10
+
11
+ Divvy was created to
12
+
13
+ - There is a difference between preparing a server and managing instances.
14
+ - Provisioning a server is more than just installing required software.
15
+ - Capistrano and Vlad are great for deployment, but are overkill for busting out SSH commands.
16
+ - Specific scripts should extend Divvy as necessary.
17
+
18
+ == Credits
19
+
20
+ Divvy was created and currently maintained by Michael Dwan.
21
+
22
+ Inspiration came from Marcus Crafter's Sprinkle, Capistrano, Evan Petrie from Metromix, and
23
+ several other scripts and frameworks I have attempted to use over the years.
24
+
25
+ == Copyright
26
+
27
+ Copyright (c) 2009 Michael Dwan. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "divvy"
8
+ gem.summary = %Q{Divvy is an easy to configure, easy to extend server provisioning framework written in Ruby.}
9
+ gem.email = "mpdwan@gmail.com"
10
+ gem.homepage = "http://github.com/mdwan/divvy"
11
+ gem.authors = ["Michael Dwan"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "divvy #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
data/bin/divvy ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "#{File.dirname(__FILE__)}/../lib/divvy"
4
+ require 'optparse'
5
+ require 'ostruct'
6
+
7
+ options = OpenStruct.new
8
+ options.verbose = false
9
+ options.test = false
10
+
11
+ parser = OptionParser.new do |opts|
12
+ opts.banner = <<BANNER
13
+ Divvy
14
+ =====
15
+
16
+ Divvy is a software provisioning tool you canuse to build remote servers with.
17
+
18
+ Usage
19
+ =====
20
+
21
+ $> #{File.basename($0)} [options]
22
+
23
+ Options are:
24
+ BANNER
25
+ opts.separator ""
26
+
27
+ opts.on("-s", "--script=PATH", "The divvy script to run") do |script|
28
+ options.script = script
29
+ end
30
+
31
+ opts.on("-t", "--[no-]test", "Process but do not perform actions") do |t|
32
+ options.test = t
33
+ end
34
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
35
+ options.verbose = v
36
+ end
37
+
38
+ opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
39
+ opts.parse!(ARGV)
40
+
41
+ unless options.script
42
+ puts "script is required"
43
+ puts opts
44
+ exit
45
+ end
46
+
47
+ end
48
+
49
+ Divvy.init
50
+
51
+ Divvy.test = options.test
52
+ Divvy.verbose = options.verbose
53
+
54
+ Divvy.run(File.read(options.script), options.script)
data/divvy.gemspec ADDED
@@ -0,0 +1,79 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{divvy}
5
+ s.version = "0.1.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Michael Dwan"]
9
+ s.date = %q{2009-05-31}
10
+ s.default_executable = %q{divvy}
11
+ s.email = %q{mpdwan@gmail.com}
12
+ s.executables = ["divvy"]
13
+ s.extra_rdoc_files = [
14
+ "LICENSE",
15
+ "README.rdoc"
16
+ ]
17
+ s.files = [
18
+ ".gitignore",
19
+ "LICENSE",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bin/divvy",
24
+ "divvy.gemspec",
25
+ "examples/packages/build_essential.rb",
26
+ "examples/packages/databases/mysql.rb",
27
+ "examples/packages/databases/sqlite3.rb",
28
+ "examples/packages/ruby/rails.rb",
29
+ "examples/packages/ruby/ruby.rb",
30
+ "examples/packages/ruby/ruby_gems.rb",
31
+ "examples/packages/scm/git.rb",
32
+ "examples/packages/utilities/screen.rb",
33
+ "lib/divvy.rb",
34
+ "lib/divvy/duplicate_package_error.rb",
35
+ "lib/divvy/package.rb",
36
+ "lib/divvy/package_runner.rb",
37
+ "lib/divvy/plugins/apt.rb",
38
+ "lib/divvy/plugins/file_utilities.rb",
39
+ "lib/divvy/plugins/gem.rb",
40
+ "lib/divvy/plugins/rails.rb",
41
+ "lib/divvy/plugins/source.rb",
42
+ "lib/divvy/provisioner.rb",
43
+ "lib/divvy/server.rb",
44
+ "lib/divvy/verification.rb",
45
+ "lib/divvy/verifiers.rb",
46
+ "test/divvy_test.rb",
47
+ "test/package_test.rb",
48
+ "test/test_helper.rb"
49
+ ]
50
+ s.has_rdoc = true
51
+ s.homepage = %q{http://github.com/mdwan/divvy}
52
+ s.rdoc_options = ["--charset=UTF-8"]
53
+ s.require_paths = ["lib"]
54
+ s.rubygems_version = %q{1.3.2}
55
+ s.summary = %q{Divvy is an easy to configure, easy to extend server provisioning framework written in Ruby.}
56
+ s.test_files = [
57
+ "test/divvy_test.rb",
58
+ "test/package_test.rb",
59
+ "test/test_helper.rb",
60
+ "examples/packages/build_essential.rb",
61
+ "examples/packages/databases/mysql.rb",
62
+ "examples/packages/databases/sqlite3.rb",
63
+ "examples/packages/ruby/rails.rb",
64
+ "examples/packages/ruby/ruby.rb",
65
+ "examples/packages/ruby/ruby_gems.rb",
66
+ "examples/packages/scm/git.rb",
67
+ "examples/packages/utilities/screen.rb"
68
+ ]
69
+
70
+ if s.respond_to? :specification_version then
71
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
72
+ s.specification_version = 3
73
+
74
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
75
+ else
76
+ end
77
+ else
78
+ end
79
+ end
@@ -0,0 +1,14 @@
1
+ Divvy.package :apt_update do
2
+ apply do
3
+ run('aptitude -y update')
4
+ run('aptitude -y safe-upgrade')
5
+ run('aptitude -y full-upgrade')
6
+ end
7
+ end
8
+
9
+ Divvy.package :build_essential do
10
+ requires :apt_update
11
+ apply do
12
+ apt %w(build-essential libssl-dev libreadline5-dev zlib1g-dev)
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ Divvy.package :mysql do
2
+ verify do
3
+ has_executable 'mysql'
4
+ end
5
+
6
+ apply do
7
+ apt %q(mysql-server mysql-client libmysqlclient15-dev)
8
+ end
9
+ end
10
+
11
+ Divvy.package :mysql_ruby_driver do
12
+ requires :mysql, :ruby, :ruby_gems
13
+ verify do
14
+ ruby_can_load 'mysql'
15
+ end
16
+
17
+ apply do
18
+ ruby_gem 'mysql'
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ Divvy.package :sqlite3 do
2
+ verify do
3
+ has_executable 'sqlite3'
4
+ end
5
+
6
+ apply do
7
+ apt 'sqlite3'
8
+ end
9
+ end
10
+
11
+ Divvy.package :sqlite3_ruby do
12
+ requires :sqlite3
13
+
14
+ verify do
15
+ ruby_can_load 'sqlite3'
16
+ end
17
+
18
+ apply do
19
+ apt 'libsqlite3-dev libsqlite3-ruby1.8'
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ Divvy.package :rails do
2
+ requires :ruby_gems
3
+
4
+ verify do
5
+ has_executable 'rails'
6
+ end
7
+
8
+ apply do
9
+ ruby_gem 'rails'
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ Divvy.package :ruby do
2
+ requires :build_essential
3
+
4
+ verify { has_file '/usr/bin/ruby1.8' }
5
+ verify { has_file '/usr/bin/ri1.8' }
6
+ verify { has_file '/usr/bin/rdoc1.8' }
7
+ verify { has_file '/usr/bin/irb1.8' }
8
+
9
+
10
+ apply do
11
+ apt %q(ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 irb1.8 libreadline-ruby1.8 libruby1.8 libopenssl-ruby)
12
+ run('ln -s /usr/bin/ruby1.8 /usr/bin/ruby')
13
+ run('ln -s /usr/bin/ri1.8 /usr/bin/ri')
14
+ run('ln -s /usr/bin/rdoc1.8 /usr/bin/rdoc')
15
+ run('ln -s /usr/bin/irb1.8 /usr/bin/irb')
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ Divvy.package :ruby_gems do
2
+ requires :build_essential, :ruby
3
+
4
+ verify do
5
+ has_executable 'gem'
6
+ end
7
+
8
+ apply do
9
+ source "http://rubyforge.org/frs/download.php/56227/rubygems-1.3.3.tgz" do
10
+ skip :configure, :build
11
+ stage :install do
12
+ run("cd #{build_dir} && ruby setup.rb")
13
+ run('ln -Fs /usr/bin/gem1.8 /usr/bin/gem')
14
+ run('gem update')
15
+ run('gem update --system')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ Divvy.package :git do
2
+ requires :build_essential
3
+
4
+ verify do
5
+ has_executable 'git'
6
+ end
7
+
8
+ apply do
9
+ apt 'git-core'
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ Divvy.package :screen do
2
+ apply do
3
+ apt 'screen'
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Divvy
2
+ class DuplicatePackageError < RuntimeError
3
+
4
+ attr_accessor :package
5
+
6
+ def initialize(package)
7
+ super("The package #{package} has already been registered")
8
+ @package = package
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ module Divvy
2
+ class Package
3
+
4
+ def initialize(name, options = {}, &block)
5
+ # raise ArgumentError.new('Name is required') unless name
6
+ @name = name.to_sym
7
+ @options = options
8
+ @dependencies = []
9
+ @verifications = []
10
+ self.instance_eval(&block)
11
+ end
12
+
13
+ attr_reader :name, :options, :verifications, :apply_block
14
+
15
+ def requires(*packages)
16
+ @dependencies << packages
17
+ @dependencies.flatten!
18
+ end
19
+
20
+ def dependencies
21
+ @dependencies.each { |package| raise "Package #{package} not found!" unless Divvy.packages.key?(package) }
22
+ @dependencies.map { |key| Divvy.packages[key] }
23
+ end
24
+
25
+ def apply(&block)
26
+ @apply_block = block
27
+ end
28
+
29
+ def verify(description = '', &block)
30
+ @verifications << Verification.new(self, description, &block)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ module Divvy
2
+ class PackageRunner
3
+ def initialize(server, package)
4
+ @server, @package = server, package
5
+ end
6
+
7
+ attr_reader :server, :package
8
+
9
+ def run(command, options = {})
10
+ server.remote_command(command, options)
11
+ end
12
+
13
+ def scp(source, destination, options = {})
14
+ server.scp(source, destination, options)
15
+ end
16
+
17
+ def process
18
+ puts "==> Processing #{package.name}"
19
+
20
+ unless package.verifications.empty?
21
+ begin
22
+ process_verifications(true)
23
+ puts " --> #{package.name} already installed package: #{server.host}"
24
+ return
25
+ rescue Divvy::VerificationFailed => e
26
+ # Yaay package not installed yet
27
+ end
28
+ end
29
+
30
+ self.instance_eval(&package.apply_block) unless package.apply_block.nil?
31
+
32
+ process_verifications
33
+ end
34
+
35
+ private
36
+ def process_verifications(pre = false)
37
+ return if package.verifications.empty?
38
+
39
+ if pre
40
+ puts " --> Checking if #{package.name} is already installed for server: #{server.host}"
41
+ else
42
+ puts " --> Verifying #{package.name} was properly installed for server: #{server.host}"
43
+ end
44
+
45
+ package.verifications.each do |v|
46
+ v.verify(server)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,21 @@
1
+ module Divvy
2
+ module Plugins
3
+ module Apt
4
+ def apt(*packages)
5
+ packages.flatten!
6
+
7
+ options = { :dependencies_only => false }
8
+ options.update(packages.pop) if packages.last.is_a?(Hash)
9
+
10
+ command = [ "DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive" ]
11
+ command << 'apt-get -qyu'
12
+ command << (options[:dependencies_only] ? 'build-dep' : 'install')
13
+ command << packages
14
+
15
+ run(command.join(' '))
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Divvy.register_plugin(Divvy::Plugins::Apt)
@@ -0,0 +1,23 @@
1
+ module Divvy
2
+ module Plugins
3
+ module FileUtilities
4
+
5
+ # Makes a directory
6
+ # path is the diretory to create. This will not create the directory if it already exists
7
+ # mode is the file mode of the directory. If nil, no mode changes will be made
8
+ def mkdir(path, mode = nil)
9
+ run("[ -d #{path} ] || mkdir -p #{path}")
10
+ run("chmod #{mode} #{dir}") unless mode.nil?
11
+ end
12
+
13
+ def push_text(path, text)
14
+ run("[ -f #{path} ] || touch #{path}")
15
+ run("echo '#{text}' | tee -a #{path}")
16
+ # "echo '#{text}' |#{'sudo' if option?(:sudo)} tee -a #{path}"
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+
23
+ Divvy.register_plugin(Divvy::Plugins::FileUtilities)
@@ -0,0 +1,21 @@
1
+ module Divvy
2
+ module Plugins
3
+ module RubyGems
4
+ def ruby_gem(gem_name, options = {})
5
+ options = {
6
+ :no_doc => true,
7
+ }.merge(options)
8
+
9
+ cmd = "gem install #{gem_name}"
10
+ cmd << " --version '#{options[:version]}'" if options[:version]
11
+ cmd << " --source #{options[:source]}" if options[:source]
12
+ cmd << " --install-dir #{options[:install_dir]}" if options[:install_dir]
13
+ cmd << " --no-rdoc --no-ri" if options[:no_doc]
14
+ cmd << " -- #{build_flags}" if options[:build_flags]
15
+ run(cmd)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Divvy.register_plugin(Divvy::Plugins::RubyGems)
@@ -0,0 +1,11 @@
1
+ module Divvy
2
+ module Plugins
3
+ module Rails
4
+ def set_rails_env(env, file = '~/.profile')
5
+ run("grep -q \"export RAILS_ENV=production\" #{file} || echo \"export RAILS_ENV=#{env}\" >> #{file}")
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ Divvy.register_plugin(Divvy::Plugins::Rails)
@@ -0,0 +1,140 @@
1
+ module Divvy
2
+ module Plugins
3
+ module Source
4
+
5
+ def source(source, options = {}, &block)
6
+ SourceInstaller.new(self, source, options, &block)
7
+ end
8
+
9
+ class SourceInstaller
10
+ def initialize(runner, source, options = {}, &block)
11
+ @runner = runner
12
+ @source = source
13
+ @options = {
14
+ :prefix => '/usr/local',
15
+ :archives => '/usr/local/sources',
16
+ :builds => '/usr/local/build'
17
+ }.merge(options)
18
+
19
+ @befores = {}
20
+ @afters = {}
21
+ @stages = {
22
+ :prepare => Proc.new { prepare },
23
+ :download => Proc.new { download },
24
+ :extract => Proc.new { extract },
25
+ :configure => Proc.new { configure },
26
+ :build => Proc.new { build },
27
+ :install => Proc.new { install }
28
+ }
29
+ self.instance_eval(&block) unless block.nil?
30
+
31
+ [:prepare, :download, :extract, :configure, :build, :install].each do |stage|
32
+ puts "before #{stage}" if Divvy.verbose
33
+ @befores[stage].call if @befores[stage]
34
+ puts "stage #{stage}" if Divvy.verbose
35
+ @stages[stage].call if @stages[stage]
36
+ puts "after #{stage}" if Divvy.verbose
37
+ @afters[stage].call if @afters[stage]
38
+ end
39
+ end
40
+
41
+ attr_reader :options, :source, :runner
42
+
43
+ def before(stage, &block)
44
+ @befores[stage] = block
45
+ end
46
+
47
+ def after(stage, &block)
48
+ @afters[stage] = block
49
+ end
50
+
51
+ def stage(stage, &block)
52
+ @stages[stage] = block
53
+ end
54
+
55
+ def skip(*stages)
56
+ stages.each { |stage| @stages.delete(stage) }
57
+ end
58
+
59
+ def build_dir #:nodoc:
60
+ "#{options[:builds]}/#{options[:custom_dir] || base_dir}"
61
+ end
62
+
63
+ def base_dir #:nodoc:
64
+ if source.split('/').last =~ /(.*)\.(tar\.gz|tgz|tar\.bz2|tb2)/
65
+ return $1
66
+ end
67
+ raise "Unknown base path for source archive: #{ssource}, please update code knowledge"
68
+ end
69
+
70
+ def archive_name
71
+ name = @source.split('/').last
72
+ raise "Unable to determine archive name for source: #{source}, please update code knowledge" unless name
73
+ name
74
+ end
75
+
76
+ private
77
+ def prepare
78
+ runner.mkdir(options[:prefix])
79
+ runner.mkdir(options[:archives])
80
+ runner.mkdir(options[:builds])
81
+ end
82
+
83
+ def download
84
+ run("wget -cq --directory-prefix='#{options[:archives]}' #{source}")
85
+ end
86
+
87
+ def extract
88
+ extract_command = case source
89
+ when /(tar.gz)|(tgz)$/
90
+ 'tar xzf'
91
+ when /(tar.bz2)|(tb2)$/
92
+ 'tar xjf'
93
+ when /tar$/
94
+ 'tar xf'
95
+ when /zip$/
96
+ 'unzip'
97
+ else
98
+ raise "Unknown source archive format: #{source}"
99
+ end
100
+
101
+ run("bash -c 'cd #{options[:builds]} && #{extract_command} #{options[:archives]}/#{archive_name}'")
102
+ end
103
+
104
+ def configure
105
+ # command = "bash -c 'cd #{build_dir} && ./configure --prefix=#{@options[:prefix]} "
106
+ command = "cd #{build_dir} && ./configure --prefix=#{@options[:prefix]} "
107
+ extras = {
108
+ :enable => '--enable', :disable => '--disable',
109
+ :with => '--with', :without => '--without'
110
+ }
111
+ extras.inject(command) { |m, (k, v)| m << create_options(k, v) if options[k]; m }
112
+
113
+ # command << " > #{archive_name}-configure.log 2>&1'"
114
+
115
+ run(command)
116
+ end
117
+
118
+ def build
119
+ # run("bash -c 'cd #{build_dir} && make > #{archive_name}-build.log 2>&1'")
120
+ run("cd #{build_dir} && make")
121
+ end
122
+
123
+ def install
124
+ # run("bash -c 'cd #{build_dir} && make install > #{archive_name}-install.log 2>&1'")
125
+ run("cd #{build_dir} && make install")
126
+ end
127
+
128
+ def create_options(key, prefix) #:nodoc:
129
+ options[key].inject(' ') { |m, option| m << "#{prefix}-#{option} "; m }
130
+ end
131
+
132
+ def method_missing(symbol, *args)
133
+ runner.send(symbol, args)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ Divvy.register_plugin(Divvy::Plugins::Source)
@@ -0,0 +1,41 @@
1
+ module Divvy
2
+ class Provisioner
3
+ def initialize(host, target_package, server_options)
4
+ @target_package = Divvy.packages[target_package]
5
+ raise "Package #{target_package} not found!" unless @target_package
6
+ @server = Server.new(host, server_options)
7
+ end
8
+
9
+ attr_reader :target_package, :server
10
+
11
+ def run
12
+ start_time = Time.now
13
+ print_package(target_package)
14
+ install_plan = normalize(target_package)
15
+ puts "Normalized install order: #{install_plan.map { |package| package.name }.join(', ')}"
16
+ install_plan.each do |package|
17
+ PackageRunner.new(server, package).process
18
+ end
19
+ puts "Took #{Time.now - start_time} seconds"
20
+ end
21
+
22
+ private
23
+ # TODO: this does not prevent circular dependencies yet
24
+ def normalize(package)
25
+ packages = []
26
+ package.dependencies.each do |dependent|
27
+ packages << normalize(dependent)
28
+ # packages << package
29
+ end
30
+ packages << package
31
+ packages.flatten.uniq
32
+ end
33
+
34
+ def print_package(package, depth = 1)
35
+ puts "#{" " * depth}#{package.name}"
36
+ package.dependencies.each do |dependent|
37
+ print_package(dependent, depth + 1)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,77 @@
1
+ require 'net/ssh'
2
+ require 'net/scp'
3
+
4
+ module Divvy
5
+ class Server
6
+ def initialize(host, options = {})
7
+ @host = host
8
+ @options = options
9
+ end
10
+
11
+ attr_reader :host, :options
12
+
13
+ def remote_command(command, options = {})
14
+ options = {
15
+ :verbose => Divvy.verbose,
16
+ :raise_on_exit_code => true,
17
+ :raise_errors => true
18
+ }.merge(options)
19
+ puts command if options[:verbose]
20
+ response_data = ''
21
+ begin
22
+ key_path = File.expand_path(self.options[:key]) if self.options[:key]
23
+ Net::SSH.start(host, self.options[:user], :password => self.options[:password], :keys => [key_path]) do |ssh|
24
+ ssh.open_channel do |channel|
25
+ channel.exec(command) do |ch, success|
26
+ raise "FAILED: couldn't execute command (ssh.channel.exec failure)" unless success
27
+
28
+ channel.on_data do |ch, data| # stdout
29
+ print data if options[:verbose]
30
+ STDOUT.flush
31
+ response_data << data
32
+ end
33
+
34
+ channel.on_extended_data do |ch, type, data|
35
+ next unless type == 1 # only handle stderr
36
+ $stderr.print data
37
+ end
38
+
39
+ channel.on_request("exit-status") do |ch, data|
40
+ exit_code = data.read_long
41
+ raise NonZeroExitCode.new(command, exit_code) if exit_code > 0 && options[:raise_on_exit_code]
42
+ end
43
+
44
+ channel.on_request("exit-signal") do |ch, data|
45
+ puts "SIGNAL: #{data.read_long}"
46
+ end
47
+ end
48
+ end
49
+ ssh.loop
50
+ end
51
+ rescue Exception => err
52
+ return false unless options[:raise_errors]
53
+ raise
54
+ end
55
+ response_data
56
+ end
57
+
58
+ def scp(source, target, options = {})
59
+ key_path = File.expand_path(self.options[:key]) if self.options[:key]
60
+ Net::SCP.start(host, self.options[:user], :password => self.options[:password], :keys => [key_path]) do |scp|
61
+ scp.upload! source, target do |ch, name, sent, total|
62
+ puts "#{name}: #{sent}/#{total}"
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ class NonZeroExitCode < Exception
69
+ def initialize(command, exit_code)
70
+ super("Non-zero exit code: #{exit_code} for #{command}")
71
+ @command = command
72
+ @exit_code = exit_code
73
+ end
74
+
75
+ attr_accessor :command, :exit_code
76
+ end
77
+ end
@@ -0,0 +1,37 @@
1
+ module Divvy
2
+ class Verification
3
+
4
+ attr_accessor :package, :description, :commands #:nodoc:
5
+
6
+ def initialize(package, description = '', &block) #:nodoc:
7
+ raise 'Verify requires a block.' unless block
8
+
9
+ @package = package
10
+ @description = description.empty? ? package.name : description
11
+ @commands = []
12
+
13
+ self.instance_eval(&block)
14
+ end
15
+
16
+ def verify(server)
17
+ @commands.each do |command|
18
+ begin
19
+ server.remote_command(command)
20
+ rescue Divvy::NonZeroExitCode => ex
21
+ raise Divvy::VerificationFailed.new(@package, description)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ class VerificationFailed < Exception #:nodoc:
28
+ attr_accessor :package, :description
29
+
30
+ def initialize(package, description)
31
+ super("Verifying #{package.name}#{description} failed.")
32
+
33
+ @package = package
34
+ @description = description
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,62 @@
1
+ module Divvy
2
+ module Verifiers
3
+ # Checks to make sure <tt>path</tt> is a file on the remote server.
4
+ def has_file(path)
5
+ @commands << "test -f #{path}"
6
+ end
7
+
8
+ def file_contains(path, text)
9
+ @commands << "grep '#{text}' #{path}"
10
+ end
11
+
12
+ # Tests that the directory <tt>dir</tt> exists.
13
+ def has_directory(dir)
14
+ @commands << "test -d #{dir}"
15
+ end
16
+
17
+ # Checks if <tt>path</tt> is an executable script. This verifier is "smart" because
18
+ # if the path contains a forward slash '/' then it assumes you're checking an
19
+ # absolute path to an executable. If no '/' is in the path, it assumes you're
20
+ # checking for a global executable that would be available anywhere on the command line.
21
+ def has_executable(path)
22
+ # Be smart: If the path includes a forward slash, we're checking
23
+ # an absolute path. Otherwise, we're checking a global executable
24
+ if path.include?('/')
25
+ @commands << "test -x #{path}"
26
+ else
27
+ @commands << "[ -n \"`echo \\`which #{path}\\``\" ]"
28
+ end
29
+ end
30
+
31
+ # Checks to make sure <tt>process</tt> is a process running
32
+ # on the remote server.
33
+ def has_process(process)
34
+ @commands << "ps aux | grep '#{process}' | grep -v grep"
35
+ end
36
+
37
+ # Checks if ruby can require the <tt>files</tt> given. <tt>rubygems</tt>
38
+ # is always included first.
39
+ def ruby_can_load(*files)
40
+ # Always include rubygems first
41
+ files = files.unshift('rubygems').collect { |x| "require '#{x}'" }
42
+
43
+ @commands << "ruby -e \"#{files.join(';')}\""
44
+ end
45
+
46
+ # Checks if a gem exists by calling "sudo gem list" and grepping against it.
47
+ def has_gem(name, version=nil)
48
+ version = version.nil? ? '' : version.gsub('.', '\.')
49
+ @commands << "sudo gem list | grep -e '^#{name} (.*#{version}.*)$'"
50
+ end
51
+
52
+ # Checks that <tt>symlink</tt> is a symbolic link. If <tt>file</tt> is
53
+ # given, it checks that <tt>symlink</tt> points to <tt>file</tt>
54
+ def has_symlink(symlink, file = nil)
55
+ if file.nil?
56
+ @commands << "test -L #{symlink}"
57
+ else
58
+ @commands << "test '#{file}' = `readlink #{symlink}`"
59
+ end
60
+ end
61
+ end
62
+ end
data/lib/divvy.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'rubygems'
2
+
3
+ Dir["#{File.dirname(__FILE__)}/divvy/*.rb"].each { |f| require f}
4
+
5
+ # class Object
6
+ # include Divvy::Script
7
+ # end
8
+
9
+ module Divvy
10
+ class << self
11
+ # user configurable
12
+ attr_accessor :test,
13
+ :verbose
14
+ # :port,
15
+ # :allow,
16
+ # :log_buffer_size,
17
+ # :pid_file_directory,
18
+ # :log_file,
19
+ # :log_level,
20
+ # :use_events
21
+
22
+ # Internal variables
23
+ attr_accessor :initialized,
24
+ # :running,
25
+ :packages
26
+ end
27
+
28
+ def self.say(message)
29
+ puts message
30
+ end
31
+
32
+ def self.init
33
+ # exit if divvy is already initialized
34
+ return if self.initialized
35
+
36
+ # variable init
37
+ self.packages = {}
38
+
39
+ # # set defaults
40
+ # self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
41
+ # self.port ||= DRB_PORT_DEFAULT
42
+ # self.allow ||= DRB_ALLOW_DEFAULT
43
+ # self.log_level ||= LOG_LEVEL_DEFAULT
44
+
45
+ # # log level
46
+ # log_level_map = {:debug => Logger::DEBUG,
47
+ # :info => Logger::INFO,
48
+ # :warn => Logger::WARN,
49
+ # :error => Logger::ERROR,
50
+ # :fatal => Logger::FATAL}
51
+ # LOG.level = log_level_map[self.log_level]
52
+
53
+ # flag as initialized
54
+ self.initialized = true
55
+
56
+ # not yet running
57
+ # self.running = false
58
+ end
59
+
60
+ def self.register_verifier(verifier)
61
+ Divvy::Verification.class_eval { include verifier }
62
+ end
63
+
64
+ def self.register_plugin(plugin)
65
+ Divvy::PackageRunner.class_eval { include plugin }
66
+ end
67
+
68
+ def self.package(name, options = {}, &block)
69
+ package = Package.new(name, options, &block)
70
+ raise DuplicatePackageError.new(package.name) if packages.key?(package.name)
71
+ packages[package.name] = package
72
+ end
73
+
74
+ def self.provision(host, package, server_options)
75
+ provision = Provisioner.new(host, package, server_options)
76
+ provision.run
77
+ end
78
+
79
+ def get_package(key)
80
+ package = PACKAGES[key]
81
+ raise "Package #{key} not found!" unless package
82
+ package
83
+ end
84
+
85
+ def self.run(script, filename = '__SCRIPT__')
86
+ Divvy.init
87
+ Object.new.instance_eval(script, filename)
88
+ end
89
+
90
+ end
91
+
92
+ Divvy.register_verifier(Divvy::Verifiers)
93
+
94
+ Dir["#{File.dirname(__FILE__)}/divvy/plugins/*"].each { |f| require f }
@@ -0,0 +1,61 @@
1
+ require 'test_helper'
2
+
3
+ class DivvyTest < Test::Unit::TestCase
4
+
5
+ context 'internal_init' do
6
+ should 'flag itself as initialized' do
7
+ Divvy.init
8
+ assert Divvy.initialized
9
+ end
10
+
11
+ should 'setup empty packages' do
12
+ Divvy.init
13
+ assert_equal Hash.new, Divvy.packages
14
+ end
15
+ end
16
+
17
+ context 'run' do
18
+ should 'call initialize' do
19
+ Divvy.expects(:init)
20
+ Divvy.run('')
21
+ end
22
+
23
+ should 'eval the script on Object' do
24
+ Object.any_instance.expects(:instance_eval).with('foobar', 'michael.rb')
25
+ Divvy.run('foobar', 'michael.rb')
26
+ end
27
+
28
+ should 'set the filename to __SCRIPT__ if no filename specified' do
29
+ Object.any_instance.expects(:instance_eval).with('foobar', '__SCRIPT__')
30
+ Divvy.run('foobar')
31
+ end
32
+ end
33
+
34
+ context 'packages' do
35
+ should 'add a package to the internal packages' do
36
+ code = <<-EOF
37
+ Divvy.package :foobar do
38
+ end
39
+ EOF
40
+
41
+ Divvy.run(code)
42
+
43
+ assert_equal 1, Divvy.packages.size
44
+ end
45
+
46
+ should 'raise DuplicatePackageError when adding duplicate package names' do
47
+ code = <<-EOF
48
+ Divvy.package :foobar do
49
+ end
50
+ Divvy.package :foobar do
51
+ end
52
+ EOF
53
+
54
+ assert_raises Divvy::DuplicatePackageError do
55
+ Divvy.run(code)
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+
3
+ class PackageTest < Test::Unit::TestCase
4
+
5
+ context 'initialize' do
6
+ should 'set name to name accessor' do
7
+ package = Divvy::Package.new :michael do
8
+ end
9
+ assert_equal :michael, package.name
10
+ end
11
+
12
+ should 'convert name strings into symbols' do
13
+ package = Divvy::Package.new 'michael' do
14
+ end
15
+ assert_equal :michael, package.name
16
+ end
17
+
18
+ should 'set dependencies to an empty array' do
19
+ package = Divvy::Package.new :name do
20
+ end
21
+ assert_equal [], package.dependencies
22
+ end
23
+
24
+ should 'set verifications to an empty array' do
25
+ package = Divvy::Package.new :name do
26
+ end
27
+ assert_equal [], package.verifications
28
+ end
29
+
30
+ should 'assign options to options accessor' do
31
+ options = { :peanut => 'butter jelly time' }
32
+ package = Divvy::Package.new :name, options do
33
+ end
34
+ assert_equal options, package.options
35
+ end
36
+
37
+ should 'assign options an empty hash if no options present' do
38
+ package = Divvy::Package.new :name do
39
+ end
40
+ assert_equal Hash.new, package.options
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'divvy'
9
+
10
+ class Test::Unit::TestCase
11
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: michaeldwan-divvy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Michael Dwan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-31 00:00:00 -07:00
13
+ default_executable: divvy
14
+ dependencies: []
15
+
16
+ description:
17
+ email: mpdwan@gmail.com
18
+ executables:
19
+ - divvy
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .gitignore
27
+ - LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - bin/divvy
32
+ - divvy.gemspec
33
+ - examples/packages/build_essential.rb
34
+ - examples/packages/databases/mysql.rb
35
+ - examples/packages/databases/sqlite3.rb
36
+ - examples/packages/ruby/rails.rb
37
+ - examples/packages/ruby/ruby.rb
38
+ - examples/packages/ruby/ruby_gems.rb
39
+ - examples/packages/scm/git.rb
40
+ - examples/packages/utilities/screen.rb
41
+ - lib/divvy.rb
42
+ - lib/divvy/duplicate_package_error.rb
43
+ - lib/divvy/package.rb
44
+ - lib/divvy/package_runner.rb
45
+ - lib/divvy/plugins/apt.rb
46
+ - lib/divvy/plugins/file_utilities.rb
47
+ - lib/divvy/plugins/gem.rb
48
+ - lib/divvy/plugins/rails.rb
49
+ - lib/divvy/plugins/source.rb
50
+ - lib/divvy/provisioner.rb
51
+ - lib/divvy/server.rb
52
+ - lib/divvy/verification.rb
53
+ - lib/divvy/verifiers.rb
54
+ - test/divvy_test.rb
55
+ - test/package_test.rb
56
+ - test/test_helper.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/mdwan/divvy
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --charset=UTF-8
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.2.0
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Divvy is an easy to configure, easy to extend server provisioning framework written in Ruby.
83
+ test_files:
84
+ - test/divvy_test.rb
85
+ - test/package_test.rb
86
+ - test/test_helper.rb
87
+ - examples/packages/build_essential.rb
88
+ - examples/packages/databases/mysql.rb
89
+ - examples/packages/databases/sqlite3.rb
90
+ - examples/packages/ruby/rails.rb
91
+ - examples/packages/ruby/ruby.rb
92
+ - examples/packages/ruby/ruby_gems.rb
93
+ - examples/packages/scm/git.rb
94
+ - examples/packages/utilities/screen.rb