sprinkle 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.markdown +6 -5
- data/Rakefile +4 -0
- data/VERSION +1 -1
- data/bin/sprinkle +5 -0
- data/lib/sprinkle.rb +7 -1
- data/lib/sprinkle/actors/ssh.rb +71 -17
- data/lib/sprinkle/actors/vlad.rb +11 -6
- data/lib/sprinkle/installers/binary.rb +1 -1
- data/lib/sprinkle/installers/deb.rb +4 -1
- data/lib/sprinkle/installers/freebsd_portinstall.rb +1 -1
- data/lib/sprinkle/installers/install_package.rb +79 -0
- data/lib/sprinkle/installers/push_text.rb +1 -1
- data/lib/sprinkle/installers/replace_text.rb +45 -0
- data/lib/sprinkle/installers/runner.rb +18 -0
- data/lib/sprinkle/installers/smart.rb +29 -0
- data/lib/sprinkle/installers/source.rb +10 -6
- data/lib/sprinkle/installers/transfer.rb +18 -4
- data/lib/sprinkle/installers/user.rb +15 -0
- data/lib/sprinkle/installers/zypper.rb +43 -0
- data/lib/sprinkle/package.rb +15 -0
- data/lib/sprinkle/verifiers/apt.rb +21 -0
- data/lib/sprinkle/verifiers/file.rb +9 -1
- data/lib/sprinkle/verifiers/package.rb +26 -0
- data/lib/sprinkle/verifiers/process.rb +2 -2
- data/lib/sprinkle/verifiers/ruby.rb +2 -2
- data/lib/sprinkle/verify.rb +2 -2
- data/spec/sprinkle/installers/push_text_spec.rb +4 -4
- data/spec/sprinkle/installers/replace_text_spec.rb +45 -0
- data/spec/sprinkle/installers/runner_spec.rb +31 -0
- data/spec/sprinkle/installers/source_spec.rb +8 -8
- data/spec/sprinkle/installers/zypper_spec.rb +49 -0
- data/spec/sprinkle/package_spec.rb +8 -0
- data/spec/sprinkle/verify_spec.rb +37 -38
- metadata +72 -21
data/.gitignore
CHANGED
data/README.markdown
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
* <http://redartisan.com/2008/5/27/sprinkle-intro>
|
4
4
|
* <http://github.com/crafterm/sprinkle>
|
5
5
|
* <http://github.com/benschwarz/passenger-stack>
|
6
|
+
* <http://github.com/trevorturk/sprinkle-packages>
|
6
7
|
* <http://www.vimeo.com/2888665>
|
7
8
|
* <http://redartisan.lighthouseapp.com/projects/25275-sprinkle/tickets>
|
8
9
|
|
@@ -23,7 +24,7 @@ An example package description follows:
|
|
23
24
|
version '1.8.6'
|
24
25
|
source "ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-#{version}-p111.tar.gz"
|
25
26
|
requires :ruby_dependencies
|
26
|
-
|
27
|
+
|
27
28
|
verify do
|
28
29
|
has_file '/usr/bin/ruby'
|
29
30
|
end
|
@@ -65,17 +66,17 @@ appserver, database and webserver can be virtual packages, where the user will b
|
|
65
66
|
multiple choices for the virtual package exist.
|
66
67
|
|
67
68
|
Sprinkle is architected to be extendable in many ways, one of those areas is in its deployment of commands to
|
68
|
-
remote hosts. Currently Sprinkle supports the use of Capistrano, Vlad, or a direct net/ssh connection to
|
69
|
+
remote hosts. Currently Sprinkle supports the use of Capistrano, Vlad, or a direct net/ssh connection to
|
69
70
|
issue commands on remote hosts via ssh, but could also be extended to use any other command transport mechanism
|
70
71
|
desired. Sprinkle can also be configured to simply issue installation commands to provision the local system.
|
71
72
|
|
72
|
-
An full example Sprinkle deployment script for deploying Rails (via gems), MySQL (via APT), Apache (via source)
|
73
|
+
An full example Sprinkle deployment script for deploying Rails (via gems), MySQL (via APT), Apache (via source)
|
73
74
|
and Git (via source with dependencies from APT):
|
74
75
|
|
75
76
|
# Sprinkle Rails deployment script
|
76
77
|
#
|
77
78
|
# This is an example Sprinkle script, configured to install Rails from gems, Apache, Ruby and Git from source,
|
78
|
-
# and mysql and Git dependencies from apt on an Ubuntu system. Installation is configured to run via
|
79
|
+
# and mysql and Git dependencies from apt on an Ubuntu system. Installation is configured to run via
|
79
80
|
# Capistrano (and an accompanying deploy.rb recipe script). Source based packages are downloaded and built into
|
80
81
|
# /usr/local on the remote system.
|
81
82
|
#
|
@@ -158,7 +159,7 @@ and Git (via source with dependencies from APT):
|
|
158
159
|
version '1.0.5'
|
159
160
|
requires :mongrel
|
160
161
|
end
|
161
|
-
|
162
|
+
|
162
163
|
package :git, :provides => :scm do
|
163
164
|
description 'Git Distributed Version Control'
|
164
165
|
version '1.5.6.3'
|
data/Rakefile
CHANGED
@@ -23,6 +23,10 @@ rescue LoadError
|
|
23
23
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
24
24
|
end
|
25
25
|
|
26
|
+
task "inst"=>[:clobber, :build] do
|
27
|
+
puts `gem install pkg/sprinkle-*.gem`
|
28
|
+
end
|
29
|
+
|
26
30
|
require 'spec/rake/spectask'
|
27
31
|
Spec::Rake::SpecTask.new(:spec) do |spec|
|
28
32
|
spec.libs << 'lib' << 'spec'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.2
|
data/bin/sprinkle
CHANGED
@@ -73,6 +73,10 @@ def powder_cloud(options)
|
|
73
73
|
Sprinkle::OPTIONS[:cloud] = OPTIONS[:cloud] || false
|
74
74
|
end
|
75
75
|
|
76
|
+
def verbosity(options)
|
77
|
+
Sprinkle::OPTIONS[:verbose] = OPTIONS[:verbose] || false
|
78
|
+
end
|
79
|
+
|
76
80
|
def log_level(options)
|
77
81
|
Object.logger.level = ActiveSupport::BufferedLogger::Severity::DEBUG if options[:verbose]
|
78
82
|
end
|
@@ -86,5 +90,6 @@ force_mode(OPTIONS)
|
|
86
90
|
operation_mode(OPTIONS)
|
87
91
|
powder_cloud(OPTIONS)
|
88
92
|
log_level(OPTIONS)
|
93
|
+
verbosity(OPTIONS)
|
89
94
|
|
90
95
|
Sprinkle::Script.sprinkle File.read(powder), powder
|
data/lib/sprinkle.rb
CHANGED
@@ -2,7 +2,13 @@ require 'rubygems'
|
|
2
2
|
require 'active_support'
|
3
3
|
|
4
4
|
# Use active supports auto load mechanism
|
5
|
-
|
5
|
+
require 'active_support/version'
|
6
|
+
if ActiveSupport::VERSION::MAJOR > 2
|
7
|
+
require 'active_support/dependencies'
|
8
|
+
ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
|
9
|
+
else
|
10
|
+
ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__)
|
11
|
+
end
|
6
12
|
|
7
13
|
# Configure active support to log auto-loading of dependencies
|
8
14
|
#ActiveSupport::Dependencies::RAILS_DEFAULT_LOGGER = Logger.new($stdout)
|
data/lib/sprinkle/actors/ssh.rb
CHANGED
@@ -29,7 +29,9 @@ module Sprinkle
|
|
29
29
|
|
30
30
|
def process(name, commands, roles, suppress_and_return_failures = false)
|
31
31
|
return process_with_gateway(name, commands, roles) if gateway_defined?
|
32
|
-
process_direct(name, commands, roles)
|
32
|
+
r = process_direct(name, commands, roles)
|
33
|
+
logger.debug green "process returning #{r}"
|
34
|
+
return r
|
33
35
|
end
|
34
36
|
|
35
37
|
def transfer(name, source, destination, roles, recursive = true, suppress_and_return_failures = false)
|
@@ -40,13 +42,17 @@ module Sprinkle
|
|
40
42
|
protected
|
41
43
|
|
42
44
|
def process_with_gateway(name, commands, roles)
|
45
|
+
res = []
|
43
46
|
on_gateway do |gateway|
|
44
|
-
Array(roles).each { |role| execute_on_role(commands, role, gateway) }
|
47
|
+
Array(roles).each { |role| res << execute_on_role(commands, role, gateway) }
|
45
48
|
end
|
49
|
+
!(res.include? false)
|
46
50
|
end
|
47
51
|
|
48
52
|
def process_direct(name, commands, roles)
|
49
|
-
|
53
|
+
res = []
|
54
|
+
Array(roles).each { |role| res << execute_on_role(commands, role) }
|
55
|
+
!(res.include? false)
|
50
56
|
end
|
51
57
|
|
52
58
|
def transfer_with_gateway(name, source, destination, roles, recursive)
|
@@ -61,7 +67,9 @@ module Sprinkle
|
|
61
67
|
|
62
68
|
def execute_on_role(commands, role, gateway = nil)
|
63
69
|
hosts = @options[:roles][role]
|
64
|
-
|
70
|
+
res = []
|
71
|
+
Array(hosts).each { |host| res << execute_on_host(commands, host, gateway) }
|
72
|
+
!(res.include? false)
|
65
73
|
end
|
66
74
|
|
67
75
|
def transfer_to_role(source, destination, role, gateway = nil)
|
@@ -70,23 +78,54 @@ module Sprinkle
|
|
70
78
|
end
|
71
79
|
|
72
80
|
def execute_on_host(commands, host, gateway = nil)
|
81
|
+
res = nil
|
82
|
+
logger.debug(blue "executing #{commands.inspect} on #{host}.")
|
73
83
|
if gateway # SSH connection via gateway
|
74
84
|
gateway.ssh(host, @options[:user]) do |ssh|
|
75
|
-
execute_on_connection(commands, ssh)
|
85
|
+
res = execute_on_connection(commands, ssh)
|
86
|
+
ssh.loop
|
76
87
|
end
|
77
88
|
else # direct SSH connection
|
78
89
|
Net::SSH.start(host, @options[:user], :password => @options[:password]) do |ssh|
|
79
|
-
execute_on_connection(commands, ssh)
|
90
|
+
res = execute_on_connection(commands, ssh)
|
91
|
+
ssh.loop
|
80
92
|
end
|
81
93
|
end
|
94
|
+
res.detect{|x| x!=0}.nil?
|
82
95
|
end
|
83
|
-
|
84
|
-
def execute_on_connection(commands,
|
85
|
-
|
86
|
-
|
87
|
-
|
96
|
+
|
97
|
+
def execute_on_connection(commands, session)
|
98
|
+
res = []
|
99
|
+
Array(commands).each do |cmd|
|
100
|
+
session.open_channel do |channel|
|
101
|
+
channel.on_data do |ch, data|
|
102
|
+
logger.debug yellow("stdout said-->\n#{data}\n")
|
103
|
+
end
|
104
|
+
channel.on_extended_data do |ch, type, data|
|
105
|
+
next unless type == 1 # only handle stderr
|
106
|
+
logger.debug red("stderr said -->\n#{data}\n")
|
107
|
+
end
|
108
|
+
|
109
|
+
channel.on_request("exit-status") do |ch, data|
|
110
|
+
exit_code = data.read_long
|
111
|
+
if exit_code == 0
|
112
|
+
logger.debug(green 'success')
|
113
|
+
else
|
114
|
+
logger.debug(red('failed (%d).'%exit_code))
|
115
|
+
end
|
116
|
+
res << exit_code
|
117
|
+
end
|
118
|
+
|
119
|
+
channel.on_request("exit-signal") do |ch, data|
|
120
|
+
logger.debug red("#{cmd} was signaled!: #{data.read_long}")
|
121
|
+
end
|
122
|
+
|
123
|
+
channel.exec cmd do |ch, status|
|
124
|
+
logger.error("couldn't run remote command #{cmd}") unless status
|
125
|
+
end
|
88
126
|
end
|
89
127
|
end
|
128
|
+
res
|
90
129
|
end
|
91
130
|
|
92
131
|
def transfer_to_host(source, destination, host, recursive, gateway = nil)
|
@@ -100,18 +139,33 @@ module Sprinkle
|
|
100
139
|
end
|
101
140
|
end
|
102
141
|
end
|
103
|
-
|
142
|
+
|
104
143
|
def transfer_on_connection(source, destination, recursive, connection)
|
105
|
-
|
106
|
-
|
144
|
+
scp = Net::SCP.new(connection)
|
145
|
+
scp.upload! source, destination, :recursive => recursive
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
def color(code, s)
|
150
|
+
"\033[%sm%s\033[0m"%[code,s]
|
151
|
+
end
|
152
|
+
def red(s)
|
153
|
+
color(31, s)
|
154
|
+
end
|
155
|
+
def yellow(s)
|
156
|
+
color(33, s)
|
157
|
+
end
|
158
|
+
def green(s)
|
159
|
+
color(32, s)
|
160
|
+
end
|
161
|
+
def blue(s)
|
162
|
+
color(34, s)
|
107
163
|
end
|
108
164
|
|
109
|
-
private
|
110
|
-
|
111
165
|
def gateway_defined?
|
112
166
|
!! @options[:gateway]
|
113
167
|
end
|
114
|
-
|
168
|
+
|
115
169
|
def on_gateway(&block)
|
116
170
|
gateway = Net::SSH::Gateway.new(@options[:gateway], @options[:user])
|
117
171
|
block.call gateway
|
data/lib/sprinkle/actors/vlad.rb
CHANGED
@@ -13,7 +13,7 @@ module Sprinkle
|
|
13
13
|
# end
|
14
14
|
# end
|
15
15
|
#
|
16
|
-
# script is given a list of files which
|
16
|
+
# script is given a list of files which vlad will include and load.
|
17
17
|
# These recipes are mainly to set variables such as :user, :password, and to
|
18
18
|
# set the app domain which will be sprinkled.
|
19
19
|
class Vlad
|
@@ -36,18 +36,23 @@ module Sprinkle
|
|
36
36
|
# end
|
37
37
|
def script(name)
|
38
38
|
@loaded_recipes ||= []
|
39
|
-
|
40
|
-
@loaded_recipes <<
|
39
|
+
require name
|
40
|
+
@loaded_recipes << name
|
41
41
|
end
|
42
42
|
|
43
43
|
def process(name, commands, roles, suppress_and_return_failures = false) #:nodoc:
|
44
|
-
commands = commands
|
44
|
+
commands = Array(commands)
|
45
|
+
if use_sudo
|
46
|
+
commands = commands.map{|x| "sudo #{x}"}
|
47
|
+
end
|
48
|
+
commands = commands.join(' && ')
|
49
|
+
puts "executing #{commands}"
|
45
50
|
t = remote_task(task_sym(name), :roles => roles) { run commands }
|
46
51
|
|
47
52
|
begin
|
48
53
|
t.invoke
|
49
54
|
return true
|
50
|
-
rescue ::
|
55
|
+
rescue ::Rake::CommandFailedError => e
|
51
56
|
return false if suppress_and_return_failures
|
52
57
|
|
53
58
|
# Reraise error if we're not suppressing it
|
@@ -60,7 +65,7 @@ module Sprinkle
|
|
60
65
|
begin
|
61
66
|
rsync source, destination
|
62
67
|
return true
|
63
|
-
rescue ::
|
68
|
+
rescue ::Rake::CommandFailedError => e
|
64
69
|
return false if suppress_and_return_failures
|
65
70
|
|
66
71
|
# Reraise error if we're not suppressing it
|
@@ -24,7 +24,10 @@ module Sprinkle
|
|
24
24
|
protected
|
25
25
|
|
26
26
|
def install_commands #:nodoc:
|
27
|
-
|
27
|
+
[
|
28
|
+
"wget -cq --directory-prefix=/tmp #{@packages.join(' ')}",
|
29
|
+
"dpkg -i #{@packages.collect{|p| "/tmp/#{package_name(p)}"}.join(" ")}"
|
30
|
+
]
|
28
31
|
end
|
29
32
|
|
30
33
|
private
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Sprinkle
|
2
|
+
module Installers
|
3
|
+
class InstallPackage < Installer
|
4
|
+
cattr_accessor :installer
|
5
|
+
attr_accessor :packages #:nodoc:
|
6
|
+
|
7
|
+
def initialize(parent, packages, &block) #:nodoc:
|
8
|
+
super parent, &block
|
9
|
+
if packages.is_a?(Array) && packages.first.is_a?(Array)
|
10
|
+
packages = packages.first
|
11
|
+
else
|
12
|
+
packages = [packages] unless packages.is_a? Array
|
13
|
+
end
|
14
|
+
|
15
|
+
@packages = packages
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def install_commands #:nodoc:
|
21
|
+
case installer
|
22
|
+
when :smart
|
23
|
+
"smart install #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/smart-sprinkle"
|
24
|
+
when :yum
|
25
|
+
"yum install #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/yum-sprinkle"
|
26
|
+
else
|
27
|
+
raise "Unknown InstallPackage.installer"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class UninstallPackage < Installer
|
33
|
+
attr_accessor :packages #:nodoc:
|
34
|
+
|
35
|
+
def initialize(parent, packages, &block) #:nodoc:
|
36
|
+
super parent, &block
|
37
|
+
if packages.is_a?(Array) && packages.first.is_a?(Array)
|
38
|
+
packages = packages.first
|
39
|
+
else
|
40
|
+
packages = [packages] unless packages.is_a? Array
|
41
|
+
end
|
42
|
+
|
43
|
+
@packages = packages
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def install_commands #:nodoc:
|
49
|
+
case Sprinkle::Installers::InstallPackage.installer
|
50
|
+
when :smart
|
51
|
+
"smart remove #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/smart-sprinkle"
|
52
|
+
when :yum
|
53
|
+
"yum erase #{@packages.join(' ')} -y 2>&1 | tee -a /var/log/yum-sprinkle"
|
54
|
+
else
|
55
|
+
raise "Unknown InstallPackage.installer"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Sprinkle
|
63
|
+
module Package
|
64
|
+
class Package
|
65
|
+
def install_package(*names, &block)
|
66
|
+
@installers << Sprinkle::Installers::InstallPackage.new(self, names, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def uninstall_package(*names, &block)
|
70
|
+
@installers << Sprinkle::Installers::UninstallPackage.new(self, names, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
alias_method :install_packages, :install_package
|
75
|
+
alias_method :uninstall_packages, :uninstall_package
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -37,7 +37,7 @@ module Sprinkle
|
|
37
37
|
protected
|
38
38
|
|
39
39
|
def install_commands #:nodoc:
|
40
|
-
"echo -e '#{@text.gsub("'", "'\\\\''").gsub("\n", '\n')}' |#{'sudo ' if option?(:sudo)}tee -a #{@path}"
|
40
|
+
"#{"#{'sudo ' if option?(:sudo)}grep \"^#{@text.gsub("'", "'\\\\''").gsub("\n", '\n')}$\" #{@path} ||" if option?(:idempotent) }/bin/echo -e '#{@text.gsub("'", "'\\\\''").gsub("\n", '\n')}' |#{'sudo ' if option?(:sudo)}tee -a #{@path}"
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sprinkle
|
2
|
+
module Installers
|
3
|
+
# = Replace text installer
|
4
|
+
#
|
5
|
+
# This installer replaces a text with another one in a file.
|
6
|
+
#
|
7
|
+
# == Example Usage
|
8
|
+
#
|
9
|
+
# Change ssh port in /etc/ssh/sshd_config
|
10
|
+
#
|
11
|
+
# package :magic_beans do
|
12
|
+
# replace_text 'Port 22', 'Port 2500', '/etc/ssh/sshd_config'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# If you user has access to 'sudo' and theres a file that requires
|
16
|
+
# priveledges, you can pass :sudo => true
|
17
|
+
#
|
18
|
+
# package :magic_beans do
|
19
|
+
# replace_text 'Port 22', 'Port 2500', '/etc/ssh/sshd_config', :sudo => true
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# A special verify step exists for this very installer
|
23
|
+
# its known as file_contains, it will test that a file indeed
|
24
|
+
# contains a substring that you send it.
|
25
|
+
#
|
26
|
+
class ReplaceText < Installer
|
27
|
+
attr_accessor :regex, :text, :path #:nodoc:
|
28
|
+
|
29
|
+
def initialize(parent, regex, text, path, options={}, &block) #:nodoc:
|
30
|
+
super parent, options, &block
|
31
|
+
@regex = regex
|
32
|
+
@text = text
|
33
|
+
@path = path
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def install_commands #:nodoc:
|
39
|
+
logger.info "--> Replace '#{@regex}' with '#{@text}' in file #{@path}"
|
40
|
+
"#{'sudo ' if option?(:sudo)}sed -i 's/#{@regex.gsub("'", "'\\\\''").gsub("/", "\\\\/").gsub("\n", '\n')}/#{@text.gsub("'", "'\\\\''").gsub("/", "\\\\/").gsub("\n", '\n')}/g' #{@path}"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|