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 +20 -0
- data/README.rdoc +15 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/bin/divvy +60 -0
- data/divvy.gemspec +77 -0
- data/examples/packages/build_essential.rb +14 -0
- data/examples/packages/databases/mysql.rb +20 -0
- data/examples/packages/databases/sqlite3.rb +21 -0
- data/examples/packages/ruby/rails.rb +11 -0
- data/examples/packages/ruby/ruby.rb +18 -0
- data/examples/packages/ruby/ruby_gems.rb +19 -0
- data/examples/packages/scm/git.rb +11 -0
- data/examples/packages/utilities/screen.rb +5 -0
- data/examples/test.rb +20 -0
- data/lib/divvy.rb +11 -0
- data/lib/divvy/package.rb +31 -0
- data/lib/divvy/package_runner.rb +51 -0
- data/lib/divvy/plugins/apt.rb +21 -0
- data/lib/divvy/plugins/file_utilities.rb +17 -0
- data/lib/divvy/plugins/gem.rb +21 -0
- data/lib/divvy/plugins/source.rb +140 -0
- data/lib/divvy/provisioner.rb +41 -0
- data/lib/divvy/script.rb +30 -0
- data/lib/divvy/server.rb +75 -0
- data/lib/divvy/verification.rb +37 -0
- data/lib/divvy/verifiers.rb +62 -0
- data/test/divvy_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +92 -0
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,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
|
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,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
|
data/lib/divvy/script.rb
ADDED
@@ -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
|
data/lib/divvy/server.rb
ADDED
@@ -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
|
data/test/divvy_test.rb
ADDED
data/test/test_helper.rb
ADDED
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
|