mdwan-divvy 0.1.0

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/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,15 @@
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
+ == Credits
10
+
11
+ Many of Divvy's concepts came from the crafterm-sprinkle gem.
12
+
13
+ == Copyright
14
+
15
+ 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{TODO}
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.0
data/bin/divvy ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "#{File.dirname(__FILE__)}/../lib/divvy"
4
+ require 'optparse'
5
+ require 'ostruct'
6
+
7
+ OPTIONS = {}
8
+
9
+ options = OpenStruct.new
10
+ options.verbose = false
11
+ options.test = false
12
+
13
+ parser = OptionParser.new do |opts|
14
+ opts.banner = <<BANNER
15
+ Divvy
16
+ =====
17
+
18
+ Divvy is a software provisioning tool you canuse to build remote servers with.
19
+
20
+ Usage
21
+ =====
22
+
23
+ $> #{File.basename($0)} [options]
24
+
25
+ Options are:
26
+ BANNER
27
+ opts.separator ""
28
+
29
+ opts.on("-s", "--script=PATH", "The divvy script to run") do |script|
30
+ options.script = script
31
+ end
32
+
33
+ opts.on("-t", "--[no-]test", "Process but do not perform actions") do |t|
34
+ options.test = t
35
+ end
36
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
37
+ options.verbose = v
38
+ end
39
+
40
+ opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
41
+ opts.parse!(ARGV)
42
+
43
+ unless options.script
44
+ puts "script is required"
45
+ puts opts
46
+ exit
47
+ end
48
+
49
+ end
50
+
51
+ Divvy::OPTIONS = options
52
+
53
+ class Runner
54
+ def self.divvy(script, filename = '__SCRIPT__')
55
+ thingy = new
56
+ thingy.instance_eval script, filename
57
+ end
58
+ end
59
+
60
+ Runner.divvy(File.read(Divvy::OPTIONS.script), Divvy::OPTIONS.script)
data/divvy.gemspec ADDED
@@ -0,0 +1,77 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{divvy}
5
+ s.version = "0.1.0"
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-20}
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
+ "LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "bin/divvy",
23
+ "divvy.gemspec",
24
+ "examples/packages/build_essential.rb",
25
+ "examples/packages/databases/mysql.rb",
26
+ "examples/packages/databases/sqlite3.rb",
27
+ "examples/packages/ruby/rails.rb",
28
+ "examples/packages/ruby/ruby.rb",
29
+ "examples/packages/ruby/ruby_gems.rb",
30
+ "examples/packages/scm/git.rb",
31
+ "examples/packages/utilities/screen.rb",
32
+ "examples/test.rb",
33
+ "lib/divvy.rb",
34
+ "lib/divvy/package.rb",
35
+ "lib/divvy/package_runner.rb",
36
+ "lib/divvy/plugins/apt.rb",
37
+ "lib/divvy/plugins/file_utilities.rb",
38
+ "lib/divvy/plugins/gem.rb",
39
+ "lib/divvy/plugins/source.rb",
40
+ "lib/divvy/provisioner.rb",
41
+ "lib/divvy/script.rb",
42
+ "lib/divvy/server.rb",
43
+ "lib/divvy/verification.rb",
44
+ "lib/divvy/verifiers.rb",
45
+ "test/divvy_test.rb",
46
+ "test/test_helper.rb"
47
+ ]
48
+ s.has_rdoc = true
49
+ s.homepage = %q{http://github.com/mdwan/divvy}
50
+ s.rdoc_options = ["--charset=UTF-8"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.2}
53
+ s.summary = %q{TODO}
54
+ s.test_files = [
55
+ "test/divvy_test.rb",
56
+ "test/test_helper.rb",
57
+ "examples/packages/build_essential.rb",
58
+ "examples/packages/databases/mysql.rb",
59
+ "examples/packages/databases/sqlite3.rb",
60
+ "examples/packages/ruby/rails.rb",
61
+ "examples/packages/ruby/ruby.rb",
62
+ "examples/packages/ruby/ruby_gems.rb",
63
+ "examples/packages/scm/git.rb",
64
+ "examples/packages/utilities/screen.rb",
65
+ "examples/test.rb"
66
+ ]
67
+
68
+ if s.respond_to? :specification_version then
69
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
70
+ s.specification_version = 3
71
+
72
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
73
+ else
74
+ end
75
+ else
76
+ end
77
+ end
@@ -0,0 +1,14 @@
1
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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,18 @@
1
+ package :ruby do
2
+ requires :build_essential
3
+
4
+ verify do
5
+ has_file '/usr/bin/ruby1.8'
6
+ has_file '/usr/bin/ri1.8'
7
+ has_file '/usr/bin/rdoc1.8'
8
+ has_file '/usr/bin/irb1.8'
9
+ end
10
+
11
+ apply do
12
+ apt %q(ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 irb1.8 libreadline-ruby1.8 libruby1.8 libopenssl-ruby)
13
+ run('ln -s /usr/bin/ruby1.8 /usr/bin/ruby')
14
+ run('ln -s /usr/bin/ri1.8 /usr/bin/ri')
15
+ run('ln -s /usr/bin/rdoc1.8 /usr/bin/rdoc')
16
+ run('ln -s /usr/bin/irb1.8 /usr/bin/irb')
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ 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
+ 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
+ package :screen do
2
+ apply do
3
+ apt 'screen'
4
+ end
5
+ end
data/examples/test.rb ADDED
@@ -0,0 +1,20 @@
1
+ package :mysql do
2
+ requires :foo
3
+
4
+ # verify do
5
+ #
6
+ # end
7
+ end
8
+
9
+ package :foo do
10
+
11
+ end
12
+
13
+ provision :web do
14
+ requires :mysql
15
+ end
16
+
17
+
18
+ deploy do
19
+ server :web, '67.23.25.222', :user => 'root', :password => 'web01lEXKMd'
20
+ end
data/lib/divvy.rb ADDED
@@ -0,0 +1,11 @@
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
+ register_verifier(Divvy::Verifiers)
10
+
11
+ Dir["#{File.dirname(__FILE__)}/divvy/plugins/*"].each { |f| require f }
@@ -0,0 +1,31 @@
1
+ module Divvy
2
+ class Package
3
+
4
+ def initialize(name, options = {}, &block)
5
+ @name = name
6
+ @options = options
7
+ @dependencies = []
8
+ @verifications = []
9
+ self.instance_eval(&block)
10
+ end
11
+
12
+ attr_reader :name, :options, :verifications, :apply_block
13
+
14
+ def requires(*packages)
15
+ @dependencies << packages
16
+ @dependencies.flatten!
17
+ end
18
+
19
+ def dependencies
20
+ @dependencies.map { |key| Divvy::Script.get_package(key) }
21
+ end
22
+
23
+ def apply(&block)
24
+ @apply_block = block
25
+ end
26
+
27
+ def verify(description = '', &block)
28
+ @verifications << Verification.new(self, description, &block)
29
+ end
30
+ end
31
+ 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
+ register_plugin(Divvy::Plugins::Apt)
@@ -0,0 +1,17 @@
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
+ end
14
+ end
15
+ end
16
+
17
+ 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
+ register_plugin(Divvy::Plugins::RubyGems)
@@ -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::OPTIONS.verbose
33
+ @befores[stage].call if @befores[stage]
34
+ puts "stage #{stage}" if Divvy::OPTIONS.verbose
35
+ @stages[stage].call if @stages[stage]
36
+ puts "after #{stage}" if Divvy::OPTIONS.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
+ 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::Script::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,30 @@
1
+ module Divvy
2
+ module Script
3
+ PACKAGES = {}
4
+
5
+ def package(name, options = {}, &block)
6
+ package = Package.new(name, options, &block)
7
+ raise "Duplicate package name: #{package.name}" if PACKAGES.key?(package.name)
8
+ PACKAGES[package.name] = package
9
+ end
10
+
11
+ def provision(host, package, server_options)
12
+ provision = Provisioner.new(host, package, server_options)
13
+ provision.run
14
+ end
15
+
16
+ def register_verifier(verifier)
17
+ Divvy::Verification.class_eval { include verifier }
18
+ end
19
+
20
+ def register_plugin(plugin)
21
+ Divvy::PackageRunner.class_eval { include plugin }
22
+ end
23
+
24
+ def get_package(key)
25
+ package = PACKAGES[key]
26
+ raise "Package #{key} not found!" unless package
27
+ package
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,75 @@
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::OPTIONS.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
+ Net::SSH.start(host, self.options[:user], :password => self.options[:password]) do |ssh|
23
+ ssh.open_channel do |channel|
24
+ channel.exec(command) do |ch, success|
25
+ raise "FAILED: couldn't execute command (ssh.channel.exec failure)" unless success
26
+
27
+ channel.on_data do |ch, data| # stdout
28
+ print data if options[:verbose]
29
+ STDOUT.flush
30
+ response_data << data
31
+ end
32
+
33
+ channel.on_extended_data do |ch, type, data|
34
+ next unless type == 1 # only handle stderr
35
+ $stderr.print data
36
+ end
37
+
38
+ channel.on_request("exit-status") do |ch, data|
39
+ exit_code = data.read_long
40
+ raise NonZeroExitCode.new(command, exit_code) if exit_code > 0 && options[:raise_on_exit_code]
41
+ end
42
+
43
+ channel.on_request("exit-signal") do |ch, data|
44
+ puts "SIGNAL: #{data.read_long}"
45
+ end
46
+ end
47
+ end
48
+ ssh.loop
49
+ end
50
+ rescue Exception => err
51
+ return false unless options[:raise_errors]
52
+ raise
53
+ end
54
+ response_data
55
+ end
56
+
57
+ def scp(source, target, options = {})
58
+ Net::SCP.start(host, self.options[:user], :password => self.options[:password]) do |scp|
59
+ scp.upload! source, target do |ch, name, sent, total|
60
+ puts "#{name}: #{sent}/#{total}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ class NonZeroExitCode < Exception
67
+ def initialize(command, exit_code)
68
+ super("Non-zero exit code: #{exit_code} for #{command}")
69
+ @command = command
70
+ @exit_code = exit_code
71
+ end
72
+
73
+ attr_accessor :command, :exit_code
74
+ end
75
+ 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
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class DivvyTest < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'divvy'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mdwan-divvy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Dwan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-20 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
+ - LICENSE
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION
30
+ - bin/divvy
31
+ - divvy.gemspec
32
+ - examples/packages/build_essential.rb
33
+ - examples/packages/databases/mysql.rb
34
+ - examples/packages/databases/sqlite3.rb
35
+ - examples/packages/ruby/rails.rb
36
+ - examples/packages/ruby/ruby.rb
37
+ - examples/packages/ruby/ruby_gems.rb
38
+ - examples/packages/scm/git.rb
39
+ - examples/packages/utilities/screen.rb
40
+ - examples/test.rb
41
+ - lib/divvy.rb
42
+ - lib/divvy/package.rb
43
+ - lib/divvy/package_runner.rb
44
+ - lib/divvy/plugins/apt.rb
45
+ - lib/divvy/plugins/file_utilities.rb
46
+ - lib/divvy/plugins/gem.rb
47
+ - lib/divvy/plugins/source.rb
48
+ - lib/divvy/provisioner.rb
49
+ - lib/divvy/script.rb
50
+ - lib/divvy/server.rb
51
+ - lib/divvy/verification.rb
52
+ - lib/divvy/verifiers.rb
53
+ - test/divvy_test.rb
54
+ - test/test_helper.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/mdwan/divvy
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.2.0
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: TODO
81
+ test_files:
82
+ - test/divvy_test.rb
83
+ - test/test_helper.rb
84
+ - examples/packages/build_essential.rb
85
+ - examples/packages/databases/mysql.rb
86
+ - examples/packages/databases/sqlite3.rb
87
+ - examples/packages/ruby/rails.rb
88
+ - examples/packages/ruby/ruby.rb
89
+ - examples/packages/ruby/ruby_gems.rb
90
+ - examples/packages/scm/git.rb
91
+ - examples/packages/utilities/screen.rb
92
+ - examples/test.rb