michaeldwan-divvy 0.1.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.
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